diff --git a/go.work.sum b/go.work.sum index 8daacb001d5..6608b7dc608 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1484,6 +1484,7 @@ github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg= github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/uber-go/atomic v1.4.0 h1:yOuPqEq4ovnhEjpHmfFwsqBXDYbQeT6Nb0bwD6XnD5o= github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= +github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw= diff --git a/pkg/registry/apis/datasource/converter.go b/pkg/registry/apis/datasource/converter.go index 8d5426bfc59..72c8e824e6d 100644 --- a/pkg/registry/apis/datasource/converter.go +++ b/pkg/registry/apis/datasource/converter.go @@ -6,6 +6,7 @@ import ( "fmt" "iter" "maps" + "slices" "strconv" "strings" "time" @@ -24,11 +25,16 @@ import ( type converter struct { mapper request.NamespaceMapper - group string // the expected group - dstype string // the expected pluginId + group string // the expected group + plugin string // the expected pluginId + alias []string // optional alias for the pluginId } func (r *converter) asDataSource(ds *datasources.DataSource) (*datasourceV0.DataSource, error) { + if !(ds.Type == r.plugin || slices.Contains(r.alias, ds.Type)) { + return nil, fmt.Errorf("expected datasource type: %s %v // not: %s", r.plugin, r.alias, ds.Type) + } + obj := &datasourceV0.DataSource{ ObjectMeta: metav1.ObjectMeta{ Name: ds.UID, @@ -109,7 +115,7 @@ func (r *converter) toAddCommand(ds *datasourceV0.DataSource) (*datasources.AddD Name: ds.Spec.Title(), UID: ds.Name, OrgID: info.OrgID, - Type: r.dstype, + Type: r.plugin, Access: datasources.DsAccess(ds.Spec.Access()), URL: ds.Spec.URL(), @@ -127,8 +133,8 @@ func (r *converter) toAddCommand(ds *datasourceV0.DataSource) (*datasources.AddD cmd.JsonData = simplejson.NewFromAny(jsonData) } - cmd.SecureJsonData, err = toSecureJsonData(ds) - return cmd, err + cmd.SecureJsonData = toSecureJsonData(ds) + return cmd, nil } func (r *converter) toUpdateCommand(ds *datasourceV0.DataSource) (*datasources.UpdateDataSourceCommand, error) { @@ -144,7 +150,7 @@ func (r *converter) toUpdateCommand(ds *datasourceV0.DataSource) (*datasources.U Name: ds.Spec.Title(), UID: ds.Name, OrgID: info.OrgID, - Type: r.dstype, + Type: r.plugin, Access: datasources.DsAccess(ds.Spec.Access()), URL: ds.Spec.URL(), @@ -164,13 +170,13 @@ func (r *converter) toUpdateCommand(ds *datasourceV0.DataSource) (*datasources.U if jsonData != nil { cmd.JsonData = simplejson.NewFromAny(jsonData) } - cmd.SecureJsonData, err = toSecureJsonData(ds) + cmd.SecureJsonData = toSecureJsonData(ds) return cmd, err } -func toSecureJsonData(ds *datasourceV0.DataSource) (map[string]string, error) { +func toSecureJsonData(ds *datasourceV0.DataSource) map[string]string { if ds == nil || len(ds.Secure) < 1 { - return nil, nil + return nil } secure := map[string]string{} @@ -182,5 +188,5 @@ func toSecureJsonData(ds *datasourceV0.DataSource) (map[string]string, error) { secure[k] = "" // Weirdly, this is the best we can do with the legacy API :( } } - return secure, nil + return secure } diff --git a/pkg/registry/apis/datasource/converter_test.go b/pkg/registry/apis/datasource/converter_test.go index 382c750f541..e5953928f88 100644 --- a/pkg/registry/apis/datasource/converter_test.go +++ b/pkg/registry/apis/datasource/converter_test.go @@ -2,6 +2,7 @@ package datasource import ( "encoding/json" + "errors" "os" "path/filepath" "testing" @@ -18,24 +19,41 @@ func TestConverter(t *testing.T) { t.Run("resource to command", func(t *testing.T) { converter := converter{ mapper: types.OrgNamespaceFormatter, - dstype: "test-datasource", + plugin: "grafana-testdata-datasource", + alias: []string{"testdata"}, group: "testdata.grafana.datasource.app", } - check := []string{ - "convert-resource-A", + tests := []struct { + name string + expectedErr string + }{ + {"convert-resource-full", ""}, + {"convert-resource-empty", ""}, + {"convert-resource-invalid", "expecting APIGroup: testdata.grafana.datasource.app"}, + {"convert-resource-invalid2", "invalid stack id"}, } - for _, name := range check { - t.Run(name, func(t *testing.T) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { obj := &v0alpha1.DataSource{} - fpath := filepath.Join("testdata", name+".json") + fpath := filepath.Join("testdata", tt.name+".json") raw, err := os.ReadFile(fpath) // nolint:gosec require.NoError(t, err) err = json.Unmarshal(raw, obj) require.NoError(t, err) // The add command - fpath = filepath.Join("testdata", name+"-to-cmd-add.json") + fpath = filepath.Join("testdata", tt.name+"-to-cmd-add.json") add, err := converter.toAddCommand(obj) + if tt.expectedErr != "" { + require.ErrorContains(t, err, tt.expectedErr) + require.Nil(t, add, "cmd should be nil when error exists") + + update, err := converter.toUpdateCommand(obj) + require.ErrorContains(t, err, tt.expectedErr) + require.Nil(t, update, "cmd should be nil when error exists") + return + } + require.NoError(t, err) out, err := json.MarshalIndent(add, "", " ") require.NoError(t, err) @@ -45,9 +63,10 @@ func TestConverter(t *testing.T) { } // The update command - fpath = filepath.Join("testdata", name+"-to-cmd-update.json") + fpath = filepath.Join("testdata", tt.name+"-to-cmd-update.json") update, err := converter.toUpdateCommand(obj) require.NoError(t, err) + out, err = json.MarshalIndent(update, "", " ") require.NoError(t, err) raw, _ = os.ReadFile(fpath) // nolint:gosec @@ -63,7 +82,7 @@ func TestConverter(t *testing.T) { roundtrip, err := converter.asDataSource(ds) require.NoError(t, err) - fpath = filepath.Join("testdata", name+"-to-cmd-update-roundtrip.json") + fpath = filepath.Join("testdata", tt.name+"-to-cmd-update-roundtrip.json") out, err = json.MarshalIndent(roundtrip, "", " ") require.NoError(t, err) raw, _ = os.ReadFile(fpath) // nolint:gosec @@ -74,41 +93,59 @@ func TestConverter(t *testing.T) { } }) - t.Run("db to resource", func(t *testing.T) { + t.Run("dto to resource", func(t *testing.T) { converter := converter{ mapper: types.OrgNamespaceFormatter, - dstype: "test-datasource", + plugin: "grafana-testdata-datasource", + alias: []string{"testdata"}, group: "testdata.grafana.datasource.app", } - check := []string{ - "convert-db-B", + tests := []struct { + name string + expectedErr string + }{ + { + name: "convert-dto-testdata", + }, + { + name: "convert-dto-empty", + }, + { + name: "convert-dto-invalid", + expectedErr: "expected datasource type: grafana-testdata-datasource [testdata]", + }, } - for _, name := range check { - t.Run(name, func(t *testing.T) { + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { ds := &datasources.DataSource{} - fpath := filepath.Join("testdata", name+".json") + fpath := filepath.Join("testdata", tt.name+".json") raw, err := os.ReadFile(fpath) // nolint:gosec require.NoError(t, err) err = json.Unmarshal(raw, ds) require.NoError(t, err) - // if true { - // ds.Created = time.Unix(100000000, 0).UTC() - // ds.Updated = time.Unix(500000000, 0).UTC() - // out, _ := json.MarshalIndent(ds, "", " ") - // fmt.Printf("%s\n", string(out)) - // t.FailNow() - // } - - // As an object - fpath = filepath.Join("testdata", name+"-to-resource.json") obj, err := converter.asDataSource(ds) - require.NoError(t, err) - out, err := json.MarshalIndent(obj, "", " ") - require.NoError(t, err) - raw, _ = os.ReadFile(fpath) // nolint:gosec - if !assert.JSONEq(t, string(raw), string(out)) { - _ = os.WriteFile(fpath, out, 0600) + if tt.expectedErr != "" { + require.ErrorContains(t, err, tt.expectedErr) + require.Nil(t, obj, "object should be nil when error exists") + } else { + require.NoError(t, err) + } + + // Verify the result + fpath = filepath.Join("testdata", tt.name+"-to-resource.json") + if obj == nil { + _, err := os.Stat(fpath) + require.Error(t, err, "file should not exist") + require.True(t, errors.Is(err, os.ErrNotExist)) + } else { + out, err := json.MarshalIndent(obj, "", " ") + require.NoError(t, err) + raw, _ = os.ReadFile(fpath) // nolint:gosec + if !assert.JSONEq(t, string(raw), string(out)) { + _ = os.WriteFile(fpath, out, 0600) + } } }) } diff --git a/pkg/registry/apis/datasource/plugincontext.go b/pkg/registry/apis/datasource/plugincontext.go index 8774951e694..c98aa4b8d8f 100644 --- a/pkg/registry/apis/datasource/plugincontext.go +++ b/pkg/registry/apis/datasource/plugincontext.go @@ -77,7 +77,8 @@ func (q *cachingDatasourceProvider) GetDatasourceProvider(pluginJson plugins.JSO contextProvider: q.contextProvider, converter: &converter{ mapper: q.converter.mapper, - dstype: pluginJson.ID, + plugin: pluginJson.ID, + alias: pluginJson.AliasIDs, group: group, }, } diff --git a/pkg/registry/apis/datasource/sub_resource.go b/pkg/registry/apis/datasource/sub_resource.go index 93027767158..f6d3fc04b96 100644 --- a/pkg/registry/apis/datasource/sub_resource.go +++ b/pkg/registry/apis/datasource/sub_resource.go @@ -8,11 +8,11 @@ import ( "net/url" "strings" - "github.com/grafana/grafana-plugin-sdk-go/backend" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" + "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/plugins/httpresponsesender" ) diff --git a/pkg/registry/apis/datasource/testdata/convert-dto-empty-to-resource.json b/pkg/registry/apis/datasource/testdata/convert-dto-empty-to-resource.json new file mode 100644 index 00000000000..4b35a3e1da8 --- /dev/null +++ b/pkg/registry/apis/datasource/testdata/convert-dto-empty-to-resource.json @@ -0,0 +1,16 @@ +{ + "metadata": { + "name": "unique-identifier", + "namespace": "org-0", + "uid": "YpaSG5GQAdxtLZtF6BqQWCeYXOhbVi5C4Cg4oILnJC0X", + "generation": 8, + "creationTimestamp": "2002-03-04T01:00:00Z", + "labels": { + "grafana.app/deprecatedInternalID": "456" + } + }, + "spec": { + "jsonData": null, + "title": "Display name" + } +} \ No newline at end of file diff --git a/pkg/registry/apis/datasource/testdata/convert-dto-empty.json b/pkg/registry/apis/datasource/testdata/convert-dto-empty.json new file mode 100644 index 00000000000..f19a05a3f4a --- /dev/null +++ b/pkg/registry/apis/datasource/testdata/convert-dto-empty.json @@ -0,0 +1,8 @@ +{ + "id": 456, + "version": 8, + "name": "Display name", + "uid": "unique-identifier", + "type": "grafana-testdata-datasource", + "created": "2002-03-04T01:00:00Z" +} \ No newline at end of file diff --git a/pkg/registry/apis/datasource/testdata/convert-dto-invalid.json b/pkg/registry/apis/datasource/testdata/convert-dto-invalid.json new file mode 100644 index 00000000000..6e5a5ca2ee2 --- /dev/null +++ b/pkg/registry/apis/datasource/testdata/convert-dto-invalid.json @@ -0,0 +1,8 @@ +{ + "id": 456, + "version": 8, + "name": "Hello", + "uid": "unique-identifier", + "type": "not-valid-plugin", + "created": "2002-03-04T01:00:00Z" +} \ No newline at end of file diff --git a/pkg/registry/apis/datasource/testdata/convert-db-B-to-resource.json b/pkg/registry/apis/datasource/testdata/convert-dto-testdata-to-resource.json similarity index 96% rename from pkg/registry/apis/datasource/testdata/convert-db-B-to-resource.json rename to pkg/registry/apis/datasource/testdata/convert-dto-testdata-to-resource.json index d84a35cd755..0bfdaf4eb02 100644 --- a/pkg/registry/apis/datasource/testdata/convert-db-B-to-resource.json +++ b/pkg/registry/apis/datasource/testdata/convert-dto-testdata-to-resource.json @@ -33,7 +33,7 @@ }, "secure": { "password": { - "name": "ds-c429ac622e" + "name": "ds-d5c1b093af" } } } \ No newline at end of file diff --git a/pkg/registry/apis/datasource/testdata/convert-db-B.json b/pkg/registry/apis/datasource/testdata/convert-dto-testdata.json similarity index 92% rename from pkg/registry/apis/datasource/testdata/convert-db-B.json rename to pkg/registry/apis/datasource/testdata/convert-dto-testdata.json index 8f24c787717..8dd66e43875 100644 --- a/pkg/registry/apis/datasource/testdata/convert-db-B.json +++ b/pkg/registry/apis/datasource/testdata/convert-dto-testdata.json @@ -3,7 +3,7 @@ "version": 2, "name": "Hello", "uid": "unique-identifier", - "type": "grafana-test-datasource", + "type": "grafana-testdata-datasource", "access": "proxy", "url": "http://something/", "user": "A", diff --git a/pkg/registry/apis/datasource/testdata/convert-resource-empty-to-cmd-add.json b/pkg/registry/apis/datasource/testdata/convert-resource-empty-to-cmd-add.json new file mode 100644 index 00000000000..fc8787b0fb3 --- /dev/null +++ b/pkg/registry/apis/datasource/testdata/convert-resource-empty-to-cmd-add.json @@ -0,0 +1,15 @@ +{ + "name": "Hello testdata", + "type": "grafana-testdata-datasource", + "access": "", + "url": "", + "user": "", + "database": "", + "basicAuth": false, + "basicAuthUser": "", + "withCredentials": false, + "isDefault": false, + "jsonData": null, + "secureJsonData": null, + "uid": "cejobd88i85j4d" +} \ No newline at end of file diff --git a/pkg/registry/apis/datasource/testdata/convert-resource-empty-to-cmd-update-roundtrip.json b/pkg/registry/apis/datasource/testdata/convert-resource-empty-to-cmd-update-roundtrip.json new file mode 100644 index 00000000000..4b16a1455a2 --- /dev/null +++ b/pkg/registry/apis/datasource/testdata/convert-resource-empty-to-cmd-update-roundtrip.json @@ -0,0 +1,12 @@ +{ + "metadata": { + "name": "cejobd88i85j4d", + "namespace": "org-0", + "uid": "boDNh7zU3nXj46rOXIJI7r44qaxjs8yy9I9dOj1MyBoX", + "creationTimestamp": null + }, + "spec": { + "jsonData": null, + "title": "Hello testdata" + } +} \ No newline at end of file diff --git a/pkg/registry/apis/datasource/testdata/convert-resource-empty-to-cmd-update.json b/pkg/registry/apis/datasource/testdata/convert-resource-empty-to-cmd-update.json new file mode 100644 index 00000000000..3d5cf24700a --- /dev/null +++ b/pkg/registry/apis/datasource/testdata/convert-resource-empty-to-cmd-update.json @@ -0,0 +1,16 @@ +{ + "name": "Hello testdata", + "type": "grafana-testdata-datasource", + "access": "", + "url": "", + "user": "", + "database": "", + "basicAuth": false, + "basicAuthUser": "", + "withCredentials": false, + "isDefault": false, + "jsonData": null, + "secureJsonData": null, + "uid": "cejobd88i85j4d", + "version": 0 +} \ No newline at end of file diff --git a/pkg/registry/apis/datasource/testdata/convert-resource-empty.json b/pkg/registry/apis/datasource/testdata/convert-resource-empty.json new file mode 100644 index 00000000000..55a9d7fb1a6 --- /dev/null +++ b/pkg/registry/apis/datasource/testdata/convert-resource-empty.json @@ -0,0 +1,8 @@ +{ + "metadata": { + "name": "cejobd88i85j4d" + }, + "spec": { + "title": "Hello testdata" + } +} \ No newline at end of file diff --git a/pkg/registry/apis/datasource/testdata/convert-resource-A-to-cmd-add.json b/pkg/registry/apis/datasource/testdata/convert-resource-full-to-cmd-add.json similarity index 86% rename from pkg/registry/apis/datasource/testdata/convert-resource-A-to-cmd-add.json rename to pkg/registry/apis/datasource/testdata/convert-resource-full-to-cmd-add.json index 0bccc0467fd..83e06abc5b2 100644 --- a/pkg/registry/apis/datasource/testdata/convert-resource-A-to-cmd-add.json +++ b/pkg/registry/apis/datasource/testdata/convert-resource-full-to-cmd-add.json @@ -1,6 +1,6 @@ { "name": "Hello testdata", - "type": "test-datasource", + "type": "grafana-testdata-datasource", "access": "proxy", "url": "http://something/", "user": "", @@ -15,6 +15,7 @@ "ccc": 1.234 }, "secureJsonData": { + "extra": "", "password": "XXXX" }, "uid": "cejobd88i85j4d" diff --git a/pkg/registry/apis/datasource/testdata/convert-resource-A-to-cmd-update-roundtrip.json b/pkg/registry/apis/datasource/testdata/convert-resource-full-to-cmd-update-roundtrip.json similarity index 86% rename from pkg/registry/apis/datasource/testdata/convert-resource-A-to-cmd-update-roundtrip.json rename to pkg/registry/apis/datasource/testdata/convert-resource-full-to-cmd-update-roundtrip.json index 55d02cd7f6d..85535306cb0 100644 --- a/pkg/registry/apis/datasource/testdata/convert-resource-A-to-cmd-update-roundtrip.json +++ b/pkg/registry/apis/datasource/testdata/convert-resource-full-to-cmd-update-roundtrip.json @@ -22,8 +22,11 @@ "withCredentials": true }, "secure": { + "extra": { + "name": "ds-bb8b5d8b32" + }, "password": { - "name": "ds-fdb9f6717d" + "name": "ds-973a1eb29d" } } } \ No newline at end of file diff --git a/pkg/registry/apis/datasource/testdata/convert-resource-A-to-cmd-update.json b/pkg/registry/apis/datasource/testdata/convert-resource-full-to-cmd-update.json similarity index 86% rename from pkg/registry/apis/datasource/testdata/convert-resource-A-to-cmd-update.json rename to pkg/registry/apis/datasource/testdata/convert-resource-full-to-cmd-update.json index 0a6d779e694..13304fc001a 100644 --- a/pkg/registry/apis/datasource/testdata/convert-resource-A-to-cmd-update.json +++ b/pkg/registry/apis/datasource/testdata/convert-resource-full-to-cmd-update.json @@ -1,6 +1,6 @@ { "name": "Hello testdata", - "type": "test-datasource", + "type": "grafana-testdata-datasource", "access": "proxy", "url": "http://something/", "user": "", @@ -15,6 +15,7 @@ "ccc": 1.234 }, "secureJsonData": { + "extra": "", "password": "XXXX" }, "uid": "cejobd88i85j4d", diff --git a/pkg/registry/apis/datasource/testdata/convert-resource-A.json b/pkg/registry/apis/datasource/testdata/convert-resource-full.json similarity index 90% rename from pkg/registry/apis/datasource/testdata/convert-resource-A.json rename to pkg/registry/apis/datasource/testdata/convert-resource-full.json index 7c4cbd78678..53c7086435a 100644 --- a/pkg/registry/apis/datasource/testdata/convert-resource-A.json +++ b/pkg/registry/apis/datasource/testdata/convert-resource-full.json @@ -27,6 +27,7 @@ } }, "secure": { - "password": { "create": "XXXX" } + "password": { "create": "XXXX" }, + "extra": { "remove": true } } } \ No newline at end of file diff --git a/pkg/registry/apis/datasource/testdata/convert-resource-invalid.json b/pkg/registry/apis/datasource/testdata/convert-resource-invalid.json new file mode 100644 index 00000000000..ccd111b6f69 --- /dev/null +++ b/pkg/registry/apis/datasource/testdata/convert-resource-invalid.json @@ -0,0 +1,17 @@ +{ + "apiVersion": "something/else", + "metadata": { + "name": "cejobd88i85j4d", + "namespace": "default", + "uid": "IGIUtEQS21DtLpBG2rSGfuDoUX8cwsGrtb5aXauYeA4X", + "resourceVersion": "1745320815000", + "generation": 2, + "creationTimestamp": "2025-04-22T11:20:11Z", + "labels": { + "grafana.app/deprecatedInternalID": "12345" + } + }, + "spec": { + "title": "Hello testdata" + } +} \ No newline at end of file diff --git a/pkg/registry/apis/datasource/testdata/convert-resource-invalid2.json b/pkg/registry/apis/datasource/testdata/convert-resource-invalid2.json new file mode 100644 index 00000000000..b3f17071243 --- /dev/null +++ b/pkg/registry/apis/datasource/testdata/convert-resource-invalid2.json @@ -0,0 +1,9 @@ +{ + "metadata": { + "name": "cejobd88i85j4d", + "namespace": "stacks-invalid" + }, + "spec": { + "title": "Hello testdata" + } +} \ No newline at end of file