secure value update
This commit is contained in:
@@ -15,13 +15,18 @@ type GenericDataSource struct {
|
||||
Spec GenericDataSourceSpec `json:"spec"`
|
||||
|
||||
// Secure values placeholder (true for fields that exist)
|
||||
Secure map[string]bool `json:"secure,omitempty"`
|
||||
Secure map[string]SecureValue `json:"secure,omitempty"`
|
||||
}
|
||||
|
||||
// // swagger:ignore
|
||||
// Password string `json:"-"`
|
||||
type SecureValue struct {
|
||||
// The input is only valid for writing the value -- it is replaced on read
|
||||
Input string `json:"input,omitempty"`
|
||||
|
||||
// // swagger:ignore
|
||||
// BasicAuthPassword string `json:"-"`
|
||||
// The name identifier for this secure value
|
||||
Reference string `json:"ref,omitempty"`
|
||||
|
||||
// Value for write, this will remove the secret value
|
||||
Remove bool `json:"remove,omitempty"`
|
||||
}
|
||||
|
||||
// DsAccess represents how the datasource connects to the remote service
|
||||
|
||||
@@ -78,7 +78,7 @@ func (in *GenericDataSource) DeepCopyInto(out *GenericDataSource) {
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
if in.Secure != nil {
|
||||
in, out := &in.Secure, &out.Secure
|
||||
*out = make(map[string]bool, len(*in))
|
||||
*out = make(map[string]SecureValue, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
@@ -182,3 +182,19 @@ func (in *HealthCheckResult) DeepCopyObject() runtime.Object {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SecureValue) DeepCopyInto(out *SecureValue) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecureValue.
|
||||
func (in *SecureValue) DeepCopy() *SecureValue {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SecureValue)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.GenericDataSourceList": schema_pkg_apis_datasource_v0alpha1_GenericDataSourceList(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.GenericDataSourceSpec": schema_pkg_apis_datasource_v0alpha1_GenericDataSourceSpec(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.HealthCheckResult": schema_pkg_apis_datasource_v0alpha1_HealthCheckResult(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.SecureValue": schema_pkg_apis_datasource_v0alpha1_SecureValue(ref),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,9 +163,8 @@ func schema_pkg_apis_datasource_v0alpha1_GenericDataSource(ref common.ReferenceC
|
||||
Allows: true,
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: false,
|
||||
Type: []string{"boolean"},
|
||||
Format: "",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.SecureValue"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -175,7 +175,7 @@ func schema_pkg_apis_datasource_v0alpha1_GenericDataSource(ref common.ReferenceC
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.GenericDataSourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.GenericDataSourceSpec", "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.SecureValue", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,3 +367,36 @@ func schema_pkg_apis_datasource_v0alpha1_HealthCheckResult(ref common.ReferenceC
|
||||
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_datasource_v0alpha1_SecureValue(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"input": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The input is only valid for writing the value -- it is replaced on read",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"ref": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The name identifier for this secure value",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"remove": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Value for write, this will remove the secret value",
|
||||
Type: []string{"boolean"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/datasource/v0alpha1,GenericDataSourceSpec,User
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/datasource/v0alpha1,SecureValue,Reference
|
||||
API rule violation: streaming_list_type_json_tags,github.com/grafana/grafana/pkg/apis/datasource/v0alpha1,GenericDataSourceList,ListMeta
|
||||
|
||||
@@ -2,6 +2,7 @@ package datasource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
@@ -15,6 +16,8 @@ import (
|
||||
|
||||
type converter struct {
|
||||
mapper request.NamespaceMapper
|
||||
group string // the expected group
|
||||
dstype string // the expected pluginId
|
||||
}
|
||||
|
||||
func asConnection(ds *datasources.DataSource, ns string) (*v0alpha1.DataSourceConnection, error) {
|
||||
@@ -73,9 +76,11 @@ func (r *converter) asGenericDataSource(ds *datasources.DataSource) (*v0alpha1.G
|
||||
}
|
||||
|
||||
if ds.SecureJsonData != nil {
|
||||
cfg.Secure = make(map[string]bool)
|
||||
cfg.Secure = make(map[string]v0alpha1.SecureValue)
|
||||
for k := range ds.SecureJsonData {
|
||||
cfg.Secure[k] = true
|
||||
cfg.Secure[k] = v0alpha1.SecureValue{
|
||||
Reference: "~", // ????
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,11 +88,15 @@ func (r *converter) asGenericDataSource(ds *datasources.DataSource) (*v0alpha1.G
|
||||
}
|
||||
|
||||
func (r *converter) toAddCommand(ds *v0alpha1.GenericDataSource) (*datasources.AddDataSourceCommand, error) {
|
||||
if r.group != "" && !strings.HasPrefix(ds.APIVersion, r.group) {
|
||||
return nil, fmt.Errorf("expecting APIGroup: %s", r.group)
|
||||
}
|
||||
|
||||
cmd := &datasources.AddDataSourceCommand{
|
||||
Name: ds.Spec.Title,
|
||||
UID: ds.Name,
|
||||
Type: r.dstype,
|
||||
|
||||
Type: "???", // TODO... group > datasource type????
|
||||
Access: datasources.DsAccess(ds.Spec.Access),
|
||||
URL: ds.Spec.URL,
|
||||
Database: ds.Spec.Database,
|
||||
@@ -103,21 +112,21 @@ func (r *converter) toAddCommand(ds *v0alpha1.GenericDataSource) (*datasources.A
|
||||
cmd.JsonData = simplejson.NewFromAny(ds.Spec.JsonData.Object)
|
||||
}
|
||||
|
||||
if len(ds.Secure) > 0 {
|
||||
cmd.SecureJsonData = map[string]string{
|
||||
"TODO": "values",
|
||||
}
|
||||
}
|
||||
cmd.SecureJsonData = toSecureJsonData(ds)
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func (r *converter) toUpdateCommand(ds *v0alpha1.GenericDataSource) (*datasources.UpdateDataSourceCommand, error) {
|
||||
if r.group != "" && !strings.HasPrefix(ds.APIVersion, r.group) {
|
||||
return nil, fmt.Errorf("expecting APIGroup: %s", r.group)
|
||||
}
|
||||
|
||||
cmd := &datasources.UpdateDataSourceCommand{
|
||||
Name: ds.Spec.Title,
|
||||
UID: ds.Name,
|
||||
Type: r.dstype,
|
||||
|
||||
Type: "???", // TODO... group > datasource type????
|
||||
Access: datasources.DsAccess(ds.Spec.Access),
|
||||
URL: ds.Spec.URL,
|
||||
Database: ds.Spec.Database,
|
||||
@@ -132,8 +141,26 @@ func (r *converter) toUpdateCommand(ds *v0alpha1.GenericDataSource) (*datasource
|
||||
if len(ds.Spec.JsonData.Object) > 0 {
|
||||
cmd.JsonData = simplejson.NewFromAny(ds.Spec.JsonData.Object)
|
||||
}
|
||||
cmd.SecureJsonData = toSecureJsonData(ds)
|
||||
|
||||
// Update specific things
|
||||
// The only thing differnet from the add command???
|
||||
cmd.Version = int(ds.Generation)
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func toSecureJsonData(ds *v0alpha1.GenericDataSource) map[string]string {
|
||||
if ds == nil || len(ds.Secure) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
secure := map[string]string{}
|
||||
for k, v := range ds.Secure {
|
||||
if v.Input != "" {
|
||||
secure[k] = v.Input
|
||||
}
|
||||
if v.Remove {
|
||||
secure[k] = "" // Weirdly, this is the best we can do with the legacy API :(
|
||||
}
|
||||
}
|
||||
return secure
|
||||
}
|
||||
|
||||
@@ -16,14 +16,17 @@ import (
|
||||
func TestConverter(t *testing.T) {
|
||||
t.Run("resource to command", func(t *testing.T) {
|
||||
obj := &v0alpha1.GenericDataSource{}
|
||||
converter := converter{mapper: types.OrgNamespaceFormatter}
|
||||
converter := converter{
|
||||
mapper: types.OrgNamespaceFormatter,
|
||||
dstype: "test-datasource",
|
||||
}
|
||||
check := []string{
|
||||
"convert-testdata-A",
|
||||
}
|
||||
for _, name := range check {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
fpath := filepath.Join("testdata", name+"-input.json")
|
||||
raw, err := os.ReadFile(fpath)
|
||||
raw, err := os.ReadFile(fpath) // nolint:gosec
|
||||
require.NoError(t, err)
|
||||
err = json.Unmarshal(raw, obj)
|
||||
require.NoError(t, err)
|
||||
@@ -34,9 +37,9 @@ func TestConverter(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
out, err := json.MarshalIndent(add, "", " ")
|
||||
require.NoError(t, err)
|
||||
raw, _ = os.ReadFile(fpath)
|
||||
raw, _ = os.ReadFile(fpath) // nolint:gosec
|
||||
if !assert.JSONEq(t, string(raw), string(out)) {
|
||||
os.WriteFile(fpath, out, 0600)
|
||||
_ = os.WriteFile(fpath, out, 0600)
|
||||
}
|
||||
|
||||
// The update command
|
||||
@@ -45,9 +48,9 @@ func TestConverter(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
out, err = json.MarshalIndent(update, "", " ")
|
||||
require.NoError(t, err)
|
||||
raw, _ = os.ReadFile(fpath)
|
||||
raw, _ = os.ReadFile(fpath) // nolint:gosec
|
||||
if !assert.JSONEq(t, string(raw), string(out)) {
|
||||
os.WriteFile(fpath, out, 0600)
|
||||
_ = os.WriteFile(fpath, out, 0600)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -86,6 +86,21 @@ func (s *legacyStorage) Update(ctx context.Context, name string, objInfo rest.Up
|
||||
return nil, false, fmt.Errorf("expected a datasource object")
|
||||
}
|
||||
|
||||
oldDS, ok := obj.(*v0alpha1.GenericDataSource)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("expected a datasource object (old)")
|
||||
}
|
||||
|
||||
// Keep all the old secure values
|
||||
if len(oldDS.Secure) > 0 {
|
||||
for k, v := range oldDS.Secure {
|
||||
_, found := ds.Secure[k]
|
||||
if !found {
|
||||
ds.Secure[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ds, err = s.datasources.UpdateDataSource(ctx, ds)
|
||||
return ds, false, err
|
||||
}
|
||||
|
||||
@@ -75,12 +75,17 @@ type cachingDatasourceProvider struct {
|
||||
}
|
||||
|
||||
func (q *cachingDatasourceProvider) GetDatasourceProvider(pluginJson plugins.JSONData) PluginDatasourceProvider {
|
||||
group, _ := plugins.GetDatasourceGroupNameFromPluginID(pluginJson.ID)
|
||||
return &scopedDatasourceProvider{
|
||||
plugin: pluginJson,
|
||||
dsService: q.dsService,
|
||||
dsCache: q.dsCache,
|
||||
contextProvider: q.contextProvider,
|
||||
converter: q.converter,
|
||||
converter: &converter{
|
||||
mapper: q.converter.mapper,
|
||||
dstype: pluginJson.ID,
|
||||
group: group,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,5 +18,8 @@
|
||||
"bbb": true,
|
||||
"ccc": 1.234
|
||||
}
|
||||
},
|
||||
"secure": {
|
||||
"password": { "input": "XXXX" }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "grafana-testdata-datasource",
|
||||
"type": "???",
|
||||
"type": "test-datasource",
|
||||
"access": "proxy",
|
||||
"url": "http://something/",
|
||||
"database": "db",
|
||||
@@ -14,7 +14,9 @@
|
||||
"bbb": true,
|
||||
"ccc": 1.234
|
||||
},
|
||||
"secureJsonData": null,
|
||||
"secureJsonData": {
|
||||
"password": "XXXX"
|
||||
},
|
||||
"uid": "cejobd88i85j4d",
|
||||
"apiVersion": "",
|
||||
"IsPrunable": false
|
||||
|
||||
+4
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "grafana-testdata-datasource",
|
||||
"type": "???",
|
||||
"type": "test-datasource",
|
||||
"access": "proxy",
|
||||
"url": "http://something/",
|
||||
"user": "",
|
||||
@@ -14,7 +14,9 @@
|
||||
"bbb": true,
|
||||
"ccc": 1.234
|
||||
},
|
||||
"secureJsonData": null,
|
||||
"secureJsonData": {
|
||||
"password": "XXXX"
|
||||
},
|
||||
"version": 2,
|
||||
"uid": "cejobd88i85j4d",
|
||||
"apiVersion": "",
|
||||
|
||||
Reference in New Issue
Block a user