1package sqlstore 2 3import ( 4 "context" 5 "fmt" 6 "strings" 7 "time" 8 9 "github.com/grafana/grafana/pkg/components/simplejson" 10 "github.com/grafana/grafana/pkg/events" 11 "github.com/grafana/grafana/pkg/infra/metrics" 12 "github.com/grafana/grafana/pkg/models" 13 "github.com/grafana/grafana/pkg/util/errutil" 14 "xorm.io/xorm" 15) 16 17// GetDataSource adds a datasource to the query model by querying by org_id as well as 18// either uid (preferred), id, or name and is added to the bus. 19func (ss *SQLStore) GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error { 20 metrics.MDBDataSourceQueryByID.Inc() 21 22 return ss.WithDbSession(ctx, func(sess *DBSession) error { 23 if query.OrgId == 0 || (query.Id == 0 && len(query.Name) == 0 && len(query.Uid) == 0) { 24 return models.ErrDataSourceIdentifierNotSet 25 } 26 27 datasource := &models.DataSource{Name: query.Name, OrgId: query.OrgId, Id: query.Id, Uid: query.Uid} 28 has, err := sess.Get(datasource) 29 30 if err != nil { 31 sqlog.Error("Failed getting data source", "err", err, "uid", query.Uid, "id", query.Id, "name", query.Name, "orgId", query.OrgId) 32 return err 33 } else if !has { 34 return models.ErrDataSourceNotFound 35 } 36 37 query.Result = datasource 38 39 return nil 40 }) 41} 42 43func (ss *SQLStore) GetDataSources(query *models.GetDataSourcesQuery) error { 44 var sess *xorm.Session 45 if query.DataSourceLimit <= 0 { 46 sess = x.Where("org_id=?", query.OrgId).Asc("name") 47 } else { 48 sess = x.Limit(query.DataSourceLimit, 0).Where("org_id=?", query.OrgId).Asc("name") 49 } 50 51 query.Result = make([]*models.DataSource, 0) 52 return sess.Find(&query.Result) 53} 54 55// GetDataSourcesByType returns all datasources for a given type or an error if the specified type is an empty string 56func (ss *SQLStore) GetDataSourcesByType(query *models.GetDataSourcesByTypeQuery) error { 57 if query.Type == "" { 58 return fmt.Errorf("datasource type cannot be empty") 59 } 60 61 query.Result = make([]*models.DataSource, 0) 62 return x.Where("type=?", query.Type).Asc("id").Find(&query.Result) 63} 64 65// GetDefaultDataSource is used to get the default datasource of organization 66func (ss *SQLStore) GetDefaultDataSource(query *models.GetDefaultDataSourceQuery) error { 67 datasource := models.DataSource{} 68 69 exists, err := x.Where("org_id=? AND is_default=?", query.OrgId, true).Get(&datasource) 70 71 if !exists { 72 return models.ErrDataSourceNotFound 73 } 74 75 query.Result = &datasource 76 return err 77} 78 79// DeleteDataSource removes a datasource by org_id as well as either uid (preferred), id, or name 80// and is added to the bus. 81func (ss *SQLStore) DeleteDataSource(ctx context.Context, cmd *models.DeleteDataSourceCommand) error { 82 params := make([]interface{}, 0) 83 84 makeQuery := func(sql string, p ...interface{}) { 85 params = append(params, sql) 86 params = append(params, p...) 87 } 88 89 switch { 90 case cmd.OrgID == 0: 91 return models.ErrDataSourceIdentifierNotSet 92 case cmd.UID != "": 93 makeQuery("DELETE FROM data_source WHERE uid=? and org_id=?", cmd.UID, cmd.OrgID) 94 case cmd.ID != 0: 95 makeQuery("DELETE FROM data_source WHERE id=? and org_id=?", cmd.ID, cmd.OrgID) 96 case cmd.Name != "": 97 makeQuery("DELETE FROM data_source WHERE name=? and org_id=?", cmd.Name, cmd.OrgID) 98 default: 99 return models.ErrDataSourceIdentifierNotSet 100 } 101 102 return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { 103 result, err := sess.Exec(params...) 104 cmd.DeletedDatasourcesCount, _ = result.RowsAffected() 105 106 sess.publishAfterCommit(&events.DataSourceDeleted{ 107 Timestamp: time.Now(), 108 Name: cmd.Name, 109 ID: cmd.ID, 110 UID: cmd.UID, 111 OrgID: cmd.OrgID, 112 }) 113 114 return err 115 }) 116} 117 118func (ss *SQLStore) AddDataSource(ctx context.Context, cmd *models.AddDataSourceCommand) error { 119 return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { 120 existing := models.DataSource{OrgId: cmd.OrgId, Name: cmd.Name} 121 has, _ := sess.Get(&existing) 122 123 if has { 124 return models.ErrDataSourceNameExists 125 } 126 127 if cmd.JsonData == nil { 128 cmd.JsonData = simplejson.New() 129 } 130 131 if cmd.Uid == "" { 132 uid, err := generateNewDatasourceUid(sess, cmd.OrgId) 133 if err != nil { 134 return errutil.Wrapf(err, "Failed to generate UID for datasource %q", cmd.Name) 135 } 136 cmd.Uid = uid 137 } 138 139 ds := &models.DataSource{ 140 OrgId: cmd.OrgId, 141 Name: cmd.Name, 142 Type: cmd.Type, 143 Access: cmd.Access, 144 Url: cmd.Url, 145 User: cmd.User, 146 Password: cmd.Password, 147 Database: cmd.Database, 148 IsDefault: cmd.IsDefault, 149 BasicAuth: cmd.BasicAuth, 150 BasicAuthUser: cmd.BasicAuthUser, 151 BasicAuthPassword: cmd.BasicAuthPassword, 152 WithCredentials: cmd.WithCredentials, 153 JsonData: cmd.JsonData, 154 SecureJsonData: cmd.EncryptedSecureJsonData, 155 Created: time.Now(), 156 Updated: time.Now(), 157 Version: 1, 158 ReadOnly: cmd.ReadOnly, 159 Uid: cmd.Uid, 160 } 161 162 if _, err := sess.Insert(ds); err != nil { 163 if dialect.IsUniqueConstraintViolation(err) && strings.Contains(strings.ToLower(dialect.ErrorMessage(err)), "uid") { 164 return models.ErrDataSourceUidExists 165 } 166 return err 167 } 168 if err := updateIsDefaultFlag(ds, sess); err != nil { 169 return err 170 } 171 172 cmd.Result = ds 173 174 sess.publishAfterCommit(&events.DataSourceCreated{ 175 Timestamp: time.Now(), 176 Name: cmd.Name, 177 ID: ds.Id, 178 UID: cmd.Uid, 179 OrgID: cmd.OrgId, 180 }) 181 return nil 182 }) 183} 184 185func updateIsDefaultFlag(ds *models.DataSource, sess *DBSession) error { 186 // Handle is default flag 187 if ds.IsDefault { 188 rawSQL := "UPDATE data_source SET is_default=? WHERE org_id=? AND id <> ?" 189 if _, err := sess.Exec(rawSQL, false, ds.OrgId, ds.Id); err != nil { 190 return err 191 } 192 } 193 return nil 194} 195 196func (ss *SQLStore) UpdateDataSource(ctx context.Context, cmd *models.UpdateDataSourceCommand) error { 197 return inTransactionCtx(ctx, func(sess *DBSession) error { 198 if cmd.JsonData == nil { 199 cmd.JsonData = simplejson.New() 200 } 201 202 ds := &models.DataSource{ 203 Id: cmd.Id, 204 OrgId: cmd.OrgId, 205 Name: cmd.Name, 206 Type: cmd.Type, 207 Access: cmd.Access, 208 Url: cmd.Url, 209 User: cmd.User, 210 Password: cmd.Password, 211 Database: cmd.Database, 212 IsDefault: cmd.IsDefault, 213 BasicAuth: cmd.BasicAuth, 214 BasicAuthUser: cmd.BasicAuthUser, 215 BasicAuthPassword: cmd.BasicAuthPassword, 216 WithCredentials: cmd.WithCredentials, 217 JsonData: cmd.JsonData, 218 SecureJsonData: cmd.EncryptedSecureJsonData, 219 Updated: time.Now(), 220 ReadOnly: cmd.ReadOnly, 221 Version: cmd.Version + 1, 222 Uid: cmd.Uid, 223 } 224 225 sess.UseBool("is_default") 226 sess.UseBool("basic_auth") 227 sess.UseBool("with_credentials") 228 sess.UseBool("read_only") 229 // Make sure password are zeroed out if empty. We do this as we want to migrate passwords from 230 // plain text fields to SecureJsonData. 231 sess.MustCols("password") 232 sess.MustCols("basic_auth_password") 233 sess.MustCols("user") 234 235 var updateSession *xorm.Session 236 if cmd.Version != 0 { 237 // the reason we allow cmd.version > db.version is make it possible for people to force 238 // updates to datasources using the datasource.yaml file without knowing exactly what version 239 // a datasource have in the db. 240 updateSession = sess.Where("id=? and org_id=? and version < ?", ds.Id, ds.OrgId, ds.Version) 241 } else { 242 updateSession = sess.Where("id=? and org_id=?", ds.Id, ds.OrgId) 243 } 244 245 affected, err := updateSession.Update(ds) 246 if err != nil { 247 return err 248 } 249 250 if affected == 0 { 251 return models.ErrDataSourceUpdatingOldVersion 252 } 253 254 err = updateIsDefaultFlag(ds, sess) 255 256 cmd.Result = ds 257 return err 258 }) 259} 260 261func generateNewDatasourceUid(sess *DBSession, orgId int64) (string, error) { 262 for i := 0; i < 3; i++ { 263 uid := generateNewUid() 264 265 exists, err := sess.Where("org_id=? AND uid=?", orgId, uid).Get(&models.DataSource{}) 266 if err != nil { 267 return "", err 268 } 269 270 if !exists { 271 return uid, nil 272 } 273 } 274 275 return "", models.ErrDataSourceFailedGenerateUniqueUid 276} 277