exclude false values

This commit is contained in:
Ryan McKinley
2025-08-20 11:37:45 +03:00
parent bce28b8663
commit 500b029b25
9 changed files with 208 additions and 32 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ func (u *UnstructuredSpec) Set(key string, val any) *UnstructuredSpec {
if u.Object == nil {
u.Object = make(map[string]any)
}
if val == nil || val == "" {
if val == nil || val == "" || val == false {
delete(u.Object, key)
} else {
u.Object[key] = val
+26 -14
View File
@@ -8,6 +8,7 @@ import (
"maps"
"strconv"
"strings"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -28,19 +29,17 @@ type converter struct {
}
func (r *converter) asDataSource(ds *datasources.DataSource) (*datasourceV0.DataSource, error) {
cfg := &datasourceV0.DataSource{
obj := &datasourceV0.DataSource{
ObjectMeta: metav1.ObjectMeta{
Name: ds.UID,
Namespace: r.mapper(ds.OrgID),
CreationTimestamp: metav1.NewTime(ds.Created),
ResourceVersion: fmt.Sprintf("%d", ds.Updated.UnixMilli()),
Generation: int64(ds.Version),
Name: ds.UID,
Namespace: r.mapper(ds.OrgID),
Generation: int64(ds.Version),
},
Spec: datasourceV0.UnstructuredSpec{},
Secure: ToInlineSecureValues(ds.Type, ds.UID, maps.Keys(ds.SecureJsonData)),
}
cfg.UID = gapiutil.CalculateClusterWideUID(cfg)
cfg.Spec.SetTitle(ds.Name).
obj.UID = gapiutil.CalculateClusterWideUID(obj)
obj.Spec.SetTitle(ds.Name).
SetAccess(string(ds.Access)).
SetURL(ds.URL).
SetDatabase(ds.Database).
@@ -53,13 +52,26 @@ func (r *converter) asDataSource(ds *datasources.DataSource) (*datasourceV0.Data
SetReadOnly(ds.ReadOnly).
SetJSONData(ds.JsonData)
if ds.ID > 0 {
cfg.Labels = map[string]string{
utils.LabelKeyDeprecatedInternalID: strconv.FormatInt(ds.ID, 10),
if !ds.Created.IsZero() {
obj.CreationTimestamp = metav1.NewTime(ds.Created)
}
if !ds.Updated.IsZero() {
obj.ResourceVersion = fmt.Sprintf("%d", ds.Updated.UnixMilli())
obj.Annotations = map[string]string{
utils.AnnoKeyUpdatedTimestamp: ds.Updated.Format(time.RFC3339),
}
}
return cfg, nil
if ds.APIVersion != "" {
obj.APIVersion = fmt.Sprintf("%s/%s", r.group, ds.APIVersion)
}
if ds.ID > 0 {
obj.Labels = map[string]string{
utils.LabelKeyDeprecatedInternalID: strconv.FormatInt(ds.ID, 10),
}
}
return obj, nil
}
// ToInlineSecureValues converts secure json into InlineSecureValues with reference names
@@ -85,7 +97,7 @@ func ToInlineSecureValues(dsType string, dsUID string, keys iter.Seq[string]) co
}
func (r *converter) toAddCommand(ds *datasourceV0.DataSource) (*datasources.AddDataSourceCommand, error) {
if r.group != "" && !strings.HasPrefix(ds.APIVersion, r.group) {
if r.group != "" && ds.APIVersion != "" && !strings.HasPrefix(ds.APIVersion, r.group) {
return nil, fmt.Errorf("expecting APIGroup: %s", r.group)
}
info, err := types.ParseNamespace(ds.Namespace)
@@ -120,7 +132,7 @@ func (r *converter) toAddCommand(ds *datasourceV0.DataSource) (*datasources.AddD
}
func (r *converter) toUpdateCommand(ds *datasourceV0.DataSource) (*datasources.UpdateDataSourceCommand, error) {
if r.group != "" && !strings.HasPrefix(ds.APIVersion, r.group) {
if r.group != "" && ds.APIVersion != "" && !strings.HasPrefix(ds.APIVersion, r.group) {
return nil, fmt.Errorf("expecting APIGroup: %s", r.group)
}
info, err := types.ParseNamespace(ds.Namespace)
+62 -4
View File
@@ -11,20 +11,22 @@ import (
"github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
"github.com/grafana/grafana/pkg/services/datasources"
)
func TestConverter(t *testing.T) {
t.Run("resource to command", func(t *testing.T) {
obj := &v0alpha1.DataSource{}
converter := converter{
mapper: types.OrgNamespaceFormatter,
dstype: "test-datasource",
group: "testdata.grafana.datasource.app",
}
check := []string{
"convert-testdata-A",
"convert-resource-A",
}
for _, name := range check {
t.Run(name, func(t *testing.T) {
obj := &v0alpha1.DataSource{}
fpath := filepath.Join("testdata", name+".json")
raw, err := os.ReadFile(fpath) // nolint:gosec
require.NoError(t, err)
@@ -32,7 +34,7 @@ func TestConverter(t *testing.T) {
require.NoError(t, err)
// The add command
fpath = filepath.Join("testdata", name+"-cmd-add.json")
fpath = filepath.Join("testdata", name+"-to-cmd-add.json")
add, err := converter.toAddCommand(obj)
require.NoError(t, err)
out, err := json.MarshalIndent(add, "", " ")
@@ -43,7 +45,7 @@ func TestConverter(t *testing.T) {
}
// The update command
fpath = filepath.Join("testdata", name+"-cmd-update.json")
fpath = filepath.Join("testdata", name+"-to-cmd-update.json")
update, err := converter.toUpdateCommand(obj)
require.NoError(t, err)
out, err = json.MarshalIndent(update, "", " ")
@@ -52,6 +54,62 @@ func TestConverter(t *testing.T) {
if !assert.JSONEq(t, string(raw), string(out)) {
_ = os.WriteFile(fpath, out, 0600)
}
// Round trip the update (NOTE, not all properties will be included)
ds := &datasources.DataSource{}
err = json.Unmarshal(raw, ds) // the add command is also a DataSource
require.NoError(t, err)
roundtrip, err := converter.asDataSource(ds)
require.NoError(t, err)
fpath = filepath.Join("testdata", name+"-to-cmd-update-roundtrip.json")
out, err = json.MarshalIndent(roundtrip, "", " ")
require.NoError(t, err)
raw, _ = os.ReadFile(fpath) // nolint:gosec
if !assert.JSONEq(t, string(raw), string(out)) {
_ = os.WriteFile(fpath, out, 0600)
}
})
}
})
t.Run("db to resource", func(t *testing.T) {
converter := converter{
mapper: types.OrgNamespaceFormatter,
dstype: "test-datasource",
group: "testdata.grafana.datasource.app",
}
check := []string{
"convert-db-B",
}
for _, name := range check {
t.Run(name, func(t *testing.T) {
ds := &datasources.DataSource{}
fpath := filepath.Join("testdata", 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)
}
})
}
})
@@ -0,0 +1,39 @@
{
"apiVersion": "testdata.grafana.datasource.app/v2alpha1",
"metadata": {
"name": "unique-identifier",
"namespace": "org-0",
"uid": "YpaSG5GQAdxtLZtF6BqQWCeYXOhbVi5C4Cg4oILnJC0X",
"resourceVersion": "1083805200000",
"generation": 2,
"creationTimestamp": "2002-03-04T01:00:00Z",
"labels": {
"grafana.app/deprecatedInternalID": "1234"
},
"annotations": {
"grafana.app/updatedTimestamp": "2004-05-06T01:00:00Z"
}
},
"spec": {
"access": "proxy",
"basicAuth": true,
"basicAuthUser": "xxx",
"database": "db",
"isDefault": true,
"jsonData": {
"aaa": "bbb",
"bbb": true,
"ccc": 1.234
},
"readOnly": true,
"title": "Hello",
"url": "http://something/",
"user": "A",
"withCredentials": true
},
"secure": {
"password": {
"name": "ds-c429ac622e"
}
}
}
+27
View File
@@ -0,0 +1,27 @@
{
"id": 1234,
"version": 2,
"name": "Hello",
"uid": "unique-identifier",
"type": "grafana-test-datasource",
"access": "proxy",
"url": "http://something/",
"user": "A",
"database": "db",
"basicAuth": true,
"basicAuthUser": "xxx",
"withCredentials": true,
"isDefault": true,
"jsonData": {
"aaa": "bbb",
"bbb": true,
"ccc": 1.234
},
"secureJsonData": {
"password": "XXXX"
},
"readOnly": true,
"apiVersion": "v2alpha1",
"created": "2002-03-04T01:00:00Z",
"updated": "2004-05-06T01:00:00Z"
}
@@ -1,19 +1,21 @@
{
"name": "grafana-testdata-datasource",
"name": "Hello testdata",
"type": "test-datasource",
"access": "proxy",
"url": "http://something/",
"user": "",
"database": "db",
"basicAuth": false,
"basicAuthUser": "",
"withCredentials": false,
"basicAuth": true,
"basicAuthUser": "xxx",
"withCredentials": true,
"isDefault": true,
"jsonData": {
"aaa": "bbb",
"bbb": true,
"ccc": 1.234
},
"secureJsonData": {},
"secureJsonData": {
"password": "XXXX"
},
"uid": "cejobd88i85j4d"
}
@@ -0,0 +1,29 @@
{
"metadata": {
"name": "cejobd88i85j4d",
"namespace": "org-0",
"uid": "boDNh7zU3nXj46rOXIJI7r44qaxjs8yy9I9dOj1MyBoX",
"generation": 2,
"creationTimestamp": null
},
"spec": {
"access": "proxy",
"basicAuth": true,
"basicAuthUser": "xxx",
"database": "db",
"isDefault": true,
"jsonData": {
"aaa": "bbb",
"bbb": true,
"ccc": 1.234
},
"title": "Hello testdata",
"url": "http://something/",
"withCredentials": true
},
"secure": {
"password": {
"name": "ds-fdb9f6717d"
}
}
}
@@ -1,20 +1,22 @@
{
"name": "grafana-testdata-datasource",
"name": "Hello testdata",
"type": "test-datasource",
"access": "proxy",
"url": "http://something/",
"user": "",
"database": "db",
"basicAuth": false,
"basicAuthUser": "",
"withCredentials": false,
"basicAuth": true,
"basicAuthUser": "xxx",
"withCredentials": true,
"isDefault": true,
"jsonData": {
"aaa": "bbb",
"bbb": true,
"ccc": 1.234
},
"secureJsonData": {},
"secureJsonData": {
"password": "XXXX"
},
"uid": "cejobd88i85j4d",
"version": 2
}
@@ -5,14 +5,21 @@
"uid": "IGIUtEQS21DtLpBG2rSGfuDoUX8cwsGrtb5aXauYeA4X",
"resourceVersion": "1745320815000",
"generation": 2,
"creationTimestamp": "2025-04-22T11:20:11Z"
"creationTimestamp": "2025-04-22T11:20:11Z",
"labels": {
"grafana.app/deprecatedInternalID": "12345"
}
},
"spec": {
"title": "grafana-testdata-datasource",
"title": "Hello testdata",
"access": "proxy",
"isDefault": true,
"readOnly": true,
"url": "http://something/",
"database": "db",
"basicAuth": true,
"basicAuthUser": "xxx",
"withCredentials": true,
"jsonData": {
"aaa": "bbb",
"bbb": true,
@@ -20,6 +27,6 @@
}
},
"secure": {
"password": { "input": "XXXX" }
"password": { "create": "XXXX" }
}
}