Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provisioning: Allows specifying uid for datasource and use that in derived fields #23585

Merged
merged 13 commits into from
Apr 20, 2020
4 changes: 3 additions & 1 deletion packages/grafana-data/src/types/dataLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ export interface DataLink {
onClick?: (event: DataLinkClickEvent) => void;

// At the moment this is used for derived fields for metadata about internal linking.
meta?: any;
meta?: {
datasourceUid?: string;
};
}

export type LinkTarget = '_blank' | '_self';
Expand Down
1 change: 1 addition & 0 deletions packages/grafana-data/src/types/datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ export interface DataSourceSettings<T extends DataSourceJsonData = DataSourceJso
*/
export interface DataSourceInstanceSettings<T extends DataSourceJsonData = DataSourceJsonData> {
id: number;
uid: string;
type: string;
name: string;
meta: DataSourcePluginMeta;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export interface Props extends InputHTMLAttributes<HTMLInputElement> {
label: string;
tooltip?: PopoverContent;
labelWidth?: number;
inputWidth?: number;
// If null no width will be specified not even default one
inputWidth?: number | null;
inputEl?: React.ReactNode;
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/api/datasources.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func AddDataSource(c *models.ReqContext, cmd models.AddDataSourceCommand) Respon
cmd.OrgId = c.OrgId

if err := bus.Dispatch(&cmd); err != nil {
if err == models.ErrDataSourceNameExists {
if err == models.ErrDataSourceNameExists || err == models.ErrDataSourceUidExists {
return Error(409, err.Error(), err)
}

Expand Down
1 change: 1 addition & 0 deletions pkg/api/frontendsettings.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i

var dsMap = map[string]interface{}{
"id": ds.Id,
"uid": ds.Uid,
"type": ds.Type,
"name": ds.Name,
"url": url,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ func TestPasswordMigrationCommand(t *testing.T) {
defer session.Close()

datasources := []*models.DataSource{
{Type: "influxdb", Name: "influxdb", Password: "foobar"},
{Type: "graphite", Name: "graphite", BasicAuthPassword: "foobar"},
{Type: "prometheus", Name: "prometheus"},
{Type: "elasticsearch", Name: "elasticsearch", Password: "pwd"},
{Type: "influxdb", Name: "influxdb", Password: "foobar", Uid: "influx"},
{Type: "graphite", Name: "graphite", BasicAuthPassword: "foobar", Uid: "graphite"},
{Type: "prometheus", Name: "prometheus", Uid: "prom"},
{Type: "elasticsearch", Name: "elasticsearch", Password: "pwd", Uid: "elastic"},
}

// set required default values
Expand Down
15 changes: 10 additions & 5 deletions pkg/models/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ const (
)

var (
ErrDataSourceNotFound = errors.New("Data source not found")
ErrDataSourceNameExists = errors.New("Data source with same name already exists")
ErrDataSourceUpdatingOldVersion = errors.New("Trying to update old version of datasource")
ErrDatasourceIsReadOnly = errors.New("Data source is readonly. Can only be updated from configuration")
ErrDataSourceAccessDenied = errors.New("Data source access denied")
ErrDataSourceNotFound = errors.New("Data source not found")
ErrDataSourceNameExists = errors.New("Data source with the same name already exists")
ErrDataSourceUidExists = errors.New("Data source with the same uid already exists")
ErrDataSourceUpdatingOldVersion = errors.New("Trying to update old version of datasource")
ErrDatasourceIsReadOnly = errors.New("Data source is readonly. Can only be updated from configuration")
ErrDataSourceAccessDenied = errors.New("Data source access denied")
ErrDataSourceFailedGenerateUniqueUid = errors.New("Failed to generate unique datasource id")
)

type DsAccess string
Expand All @@ -57,6 +59,7 @@ type DataSource struct {
JsonData *simplejson.Json
SecureJsonData securejsondata.SecureJsonData
ReadOnly bool
Uid string

Created time.Time
Updated time.Time
Expand Down Expand Up @@ -144,6 +147,7 @@ type AddDataSourceCommand struct {
IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData"`
SecureJsonData map[string]string `json:"secureJsonData"`
Uid string `json:"uid"`

OrgId int64 `json:"-"`
ReadOnly bool `json:"-"`
Expand All @@ -168,6 +172,7 @@ type UpdateDataSourceCommand struct {
JsonData *simplejson.Json `json:"jsonData"`
SecureJsonData map[string]string `json:"secureJsonData"`
Version int `json:"version"`
Uid string `json:"uid"`

OrgId int64 `json:"-"`
Id int64 `json:"-"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func TestDatasourceAsConfig(t *testing.T) {

So(dsCfg.APIVersion, ShouldEqual, 1)

validateDatasource(dsCfg)
validateDatasourceV1(dsCfg)
validateDeleteDatasources(dsCfg)

dsCount := 0
Expand Down Expand Up @@ -231,6 +231,12 @@ func validateDatasource(dsCfg *configs) {
So(ds.SecureJSONData["tlsClientKey"], ShouldEqual, "ZkN4aG1aNkja/gKAB1wlnKFIsy2SRDq4slrM0A==")
}

func validateDatasourceV1(dsCfg *configs) {
validateDatasource(dsCfg)
ds := dsCfg.Datasources[0]
So(ds.Uid, ShouldEqual, "test_uid")
}

type fakeRepository struct {
inserted []*models.AddDataSourceCommand
deleted []*models.DeleteDataSourceByNameCommand
Expand Down
4 changes: 2 additions & 2 deletions pkg/services/provisioning/datasources/datasources.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ func (dc *DatasourceProvisioner) apply(cfg *configs) error {
}

if err == models.ErrDataSourceNotFound {
dc.log.Info("inserting datasource from configuration ", "name", ds.Name)
dc.log.Info("inserting datasource from configuration ", "name", ds.Name, "uid", ds.Uid)
insertCmd := createInsertCommand(ds)
if err := bus.Dispatch(insertCmd); err != nil {
return err
}
} else {
dc.log.Debug("updating datasource from configuration", "name", ds.Name)
dc.log.Debug("updating datasource from configuration", "name", ds.Name, "uid", ds.Uid)
updateCmd := createUpdateCommand(ds, cmd.Result.Id)
if err := bus.Dispatch(updateCmd); err != nil {
return err
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ datasources:
tlsClientKey: "ZkN4aG1aNkja/gKAB1wlnKFIsy2SRDq4slrM0A=="
editable: true
version: 10
uid: "test_uid"

deleteDatasources:
- name: old-graphite3
Expand Down
5 changes: 5 additions & 0 deletions pkg/services/provisioning/datasources/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type upsertDataSourceFromConfig struct {
JSONData map[string]interface{}
SecureJSONData map[string]string
Editable bool
Uid string
}

type configsV0 struct {
Expand Down Expand Up @@ -108,6 +109,7 @@ type upsertDataSourceFromConfigV1 struct {
JSONData values.JSONValue `json:"jsonData" yaml:"jsonData"`
SecureJSONData values.StringMapValue `json:"secureJsonData" yaml:"secureJsonData"`
Editable values.BoolValue `json:"editable" yaml:"editable"`
Uid values.StringValue `json:"uid" yaml:"uid"`
}

func (cfg *configsV1) mapToDatasourceFromConfig(apiVersion int64) *configs {
Expand Down Expand Up @@ -138,6 +140,7 @@ func (cfg *configsV1) mapToDatasourceFromConfig(apiVersion int64) *configs {
SecureJSONData: ds.SecureJSONData.Value(),
Editable: ds.Editable.Value(),
Version: ds.Version.Value(),
Uid: ds.Uid.Value(),
})

// Using Raw value for the warnings here so that even if it uses env interpolation and the env var is empty
Expand Down Expand Up @@ -234,6 +237,7 @@ func createInsertCommand(ds *upsertDataSourceFromConfig) *models.AddDataSourceCo
JsonData: jsonData,
SecureJsonData: ds.SecureJSONData,
ReadOnly: !ds.Editable,
Uid: ds.Uid,
}
}

Expand All @@ -247,6 +251,7 @@ func createUpdateCommand(ds *upsertDataSourceFromConfig, id int64) *models.Updat

return &models.UpdateDataSourceCommand{
Id: id,
Uid: ds.Uid,
OrgId: ds.OrgID,
Name: ds.Name,
Type: ds.Type,
Expand Down
32 changes: 32 additions & 0 deletions pkg/services/sqlstore/datasource.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package sqlstore

import (
"github.com/grafana/grafana/pkg/util/errutil"
"strings"
"time"

"github.com/grafana/grafana/pkg/components/simplejson"
Expand Down Expand Up @@ -101,6 +103,14 @@ func AddDataSource(cmd *models.AddDataSourceCommand) error {
cmd.JsonData = simplejson.New()
}

if cmd.Uid == "" {
uid, err := generateNewDatasourceUid(sess, cmd.OrgId)
if err != nil {
return errutil.Wrapf(err, "Failed to generate UID for Datasource %q", cmd.Name)
aocenas marked this conversation as resolved.
Show resolved Hide resolved
}
cmd.Uid = uid
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If uid provided and uid already exists on another data source should return error similar to models.ErrDataSourceNameExists above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added mapping from unique constraint error later on.


ds := &models.DataSource{
OrgId: cmd.OrgId,
Name: cmd.Name,
Expand All @@ -121,9 +131,13 @@ func AddDataSource(cmd *models.AddDataSourceCommand) error {
Updated: time.Now(),
Version: 1,
ReadOnly: cmd.ReadOnly,
Uid: cmd.Uid,
}

if _, err := sess.Insert(ds); err != nil {
if dialect.IsUniqueConstraintViolation(err) && strings.Contains(strings.ToLower(dialect.ErrorMessage(err)), "uid") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a bit fragile. It would be nice if dialect could let us know which column is causing the error. Its good enough for now but something I would like to improve later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @bergquist.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True but problem is that the DB errors for Mysql and sqlite do not have any more info. So could be moved to dialect and made more robust for PG but for others this would basically be the same code.

return models.ErrDataSourceUidExists
}
return err
}
if err := updateIsDefaultFlag(ds, sess); err != nil {
Expand Down Expand Up @@ -172,6 +186,7 @@ func UpdateDataSource(cmd *models.UpdateDataSourceCommand) error {
Updated: time.Now(),
ReadOnly: cmd.ReadOnly,
Version: cmd.Version + 1,
Uid: cmd.Uid,
}

sess.UseBool("is_default")
Expand Down Expand Up @@ -209,3 +224,20 @@ func UpdateDataSource(cmd *models.UpdateDataSourceCommand) error {
return err
})
}

func generateNewDatasourceUid(sess *DBSession, orgId int64) (string, error) {
for i := 0; i < 3; i++ {
uid := generateNewUid()

exists, err := sess.Where("org_id=? AND uid=?", orgId, uid).Get(&models.DataSource{})
if err != nil {
return "", err
}

if !exists {
return uid, nil
}
}

return "", models.ErrDataSourceFailedGenerateUniqueUid
}
Loading