Merge branch 'develop' into apikey_hashed

Conflicts:
	pkg/api/apikey.go
	pkg/services/sqlstore/migrations.go
This commit is contained in:
Torkel Ödegaard
2015-02-26 15:48:54 +01:00
226 changed files with 6360 additions and 5676 deletions
-50
View File
@@ -1,50 +0,0 @@
package api
import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
)
func GetAccount(c *middleware.Context) {
query := m.GetAccountByIdQuery{Id: c.AccountId}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrAccountNotFound {
c.JsonApiErr(404, "Account not found", err)
return
}
c.JsonApiErr(500, "Failed to get account", err)
return
}
account := m.AccountDTO{
Id: query.Result.Id,
Name: query.Result.Name,
}
c.JSON(200, &account)
}
func CreateAccount(c *middleware.Context, cmd m.CreateAccountCommand) {
cmd.UserId = c.UserId
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "Failed to create account", err)
return
}
c.JsonOK("Account created")
}
func UpdateAccount(c *middleware.Context, cmd m.UpdateAccountCommand) {
cmd.AccountId = c.AccountId
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "Failed to update account", err)
return
}
c.JsonOK("Account updated")
}
+29
View File
@@ -0,0 +1,29 @@
package api
import (
"strings"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/setting"
)
func AdminGetSettings(c *middleware.Context) {
settings := make(map[string]interface{})
for _, section := range setting.Cfg.Sections() {
jsonSec := make(map[string]interface{})
settings[section.Name()] = jsonSec
for _, key := range section.Keys() {
keyName := key.Name()
value := key.Value()
if strings.Contains(keyName, "secret") || strings.Contains(keyName, "password") {
value = "************"
}
jsonSec[keyName] = value
}
}
c.JSON(200, settings)
}
+138 -5
View File
@@ -1,20 +1,153 @@
package api
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/util"
)
func AdminSearchUsers(c *middleware.Context) {
// query := c.QueryStrings("q")
// page := c.QueryStrings("p")
query := m.SearchUsersQuery{Query: "", Page: 0, Limit: 20}
query := m.SearchUsersQuery{Query: "", Page: 0, Limit: 1000}
if err := bus.Dispatch(&query); err != nil {
c.JsonApiErr(500, "Failed to fetch collaboratos", err)
c.JsonApiErr(500, "Failed to fetch users", err)
return
}
c.JSON(200, query.Result)
}
func AdminGetUser(c *middleware.Context) {
userId := c.ParamsInt64(":id")
query := m.GetUserByIdQuery{Id: userId}
if err := bus.Dispatch(&query); err != nil {
c.JsonApiErr(500, "Failed to fetch user", err)
return
}
result := m.UserDTO{
Name: query.Result.Name,
Email: query.Result.Email,
Login: query.Result.Login,
IsGrafanaAdmin: query.Result.IsAdmin,
}
c.JSON(200, result)
}
func AdminCreateUser(c *middleware.Context, form dtos.AdminCreateUserForm) {
cmd := m.CreateUserCommand{
Login: form.Login,
Email: form.Email,
Password: form.Password,
Name: form.Name,
}
if len(cmd.Login) == 0 {
cmd.Login = cmd.Email
if len(cmd.Login) == 0 {
c.JsonApiErr(400, "Validation error, need specify either username or email", nil)
return
}
}
if len(cmd.Password) < 4 {
c.JsonApiErr(400, "Password is missing or too short", nil)
return
}
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "failed to create user", err)
return
}
c.JsonOK("User created")
}
func AdminUpdateUser(c *middleware.Context, form dtos.AdminUpdateUserForm) {
userId := c.ParamsInt64(":id")
cmd := m.UpdateUserCommand{
UserId: userId,
Login: form.Login,
Email: form.Email,
Name: form.Name,
}
if len(cmd.Login) == 0 {
cmd.Login = cmd.Email
if len(cmd.Login) == 0 {
c.JsonApiErr(400, "Validation error, need specify either username or email", nil)
return
}
}
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "failed to update user", err)
return
}
c.JsonOK("User updated")
}
func AdminUpdateUserPassword(c *middleware.Context, form dtos.AdminUpdateUserPasswordForm) {
userId := c.ParamsInt64(":id")
if len(form.Password) < 4 {
c.JsonApiErr(400, "New password too short", nil)
return
}
userQuery := m.GetUserByIdQuery{Id: userId}
if err := bus.Dispatch(&userQuery); err != nil {
c.JsonApiErr(500, "Could not read user from database", err)
return
}
passwordHashed := util.EncodePassword(form.Password, userQuery.Result.Salt)
cmd := m.ChangeUserPasswordCommand{
UserId: userId,
NewPassword: passwordHashed,
}
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "Failed to update user password", err)
return
}
c.JsonOK("User password updated")
}
func AdminUpdateUserPermissions(c *middleware.Context, form dtos.AdminUpdateUserPermissionsForm) {
userId := c.ParamsInt64(":id")
cmd := m.UpdateUserPermissionsCommand{
UserId: userId,
IsGrafanaAdmin: form.IsGrafanaAdmin,
}
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "Failed to update user permissions", err)
return
}
c.JsonOK("User permissions updated")
}
func AdminDeleteUser(c *middleware.Context) {
userId := c.ParamsInt64(":id")
cmd := m.DeleteUserCommand{UserId: userId}
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "Failed to delete user", err)
return
}
c.JsonOK("User deleted")
}
+29 -15
View File
@@ -25,12 +25,15 @@ func Register(r *macaron.Macaron) {
// authed views
r.Get("/profile/", reqSignedIn, Index)
r.Get("/account/", reqSignedIn, Index)
r.Get("/account/datasources/", reqSignedIn, Index)
r.Get("/account/users/", reqSignedIn, Index)
r.Get("/account/apikeys/", reqSignedIn, Index)
r.Get("/account/import/", reqSignedIn, Index)
r.Get("/org/", reqSignedIn, Index)
r.Get("/datasources/", reqSignedIn, Index)
r.Get("/org/users/", reqSignedIn, Index)
r.Get("/org/apikeys/", reqSignedIn, Index)
r.Get("/dashboard/import/", reqSignedIn, Index)
r.Get("/admin/settings", reqGrafanaAdmin, Index)
r.Get("/admin/users", reqGrafanaAdmin, Index)
r.Get("/admin/users/create", reqGrafanaAdmin, Index)
r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index)
r.Get("/dashboard/*", reqSignedIn, Index)
// sign up
@@ -43,20 +46,21 @@ func Register(r *macaron.Macaron) {
r.Group("/user", func() {
r.Get("/", GetUser)
r.Put("/", bind(m.UpdateUserCommand{}), UpdateUser)
r.Post("/using/:id", SetUsingAccount)
r.Get("/accounts", GetUserAccounts)
r.Post("/using/:id", UserSetUsingOrg)
r.Get("/orgs", GetUserOrgList)
r.Post("/stars/dashboard/:id", StarDashboard)
r.Delete("/stars/dashboard/:id", UnstarDashboard)
r.Put("/password", bind(m.ChangeUserPasswordCommand{}), ChangeUserPassword)
})
// account
r.Group("/account", func() {
r.Get("/", GetAccount)
r.Post("/", bind(m.CreateAccountCommand{}), CreateAccount)
r.Put("/", bind(m.UpdateAccountCommand{}), UpdateAccount)
r.Post("/users", bind(m.AddAccountUserCommand{}), AddAccountUser)
r.Get("/users", GetAccountUsers)
r.Delete("/users/:id", RemoveAccountUser)
r.Group("/org", func() {
r.Get("/", GetOrg)
r.Post("/", bind(m.CreateOrgCommand{}), CreateOrg)
r.Put("/", bind(m.UpdateOrgCommand{}), UpdateOrg)
r.Post("/users", bind(m.AddOrgUserCommand{}), AddOrgUser)
r.Get("/users", GetOrgUsers)
r.Delete("/users/:id", RemoveOrgUser)
}, reqAccountAdmin)
// auth api keys
@@ -70,9 +74,12 @@ func Register(r *macaron.Macaron) {
r.Group("/datasources", func() {
r.Combo("/").Get(GetDataSources).Put(AddDataSource).Post(UpdateDataSource)
r.Delete("/:id", DeleteDataSource)
r.Any("/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
r.Get("/:id", GetDataSourceById)
}, reqAccountAdmin)
r.Get("/frontend/settings/", GetFrontendSettings)
r.Any("/datasources/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
// Dashboard
r.Group("/dashboards", func() {
r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard)
@@ -89,7 +96,14 @@ func Register(r *macaron.Macaron) {
// admin api
r.Group("/api/admin", func() {
r.Get("/settings", AdminGetSettings)
r.Get("/users", AdminSearchUsers)
r.Get("/users/:id", AdminGetUser)
r.Post("/users", bind(dtos.AdminCreateUserForm{}), AdminCreateUser)
r.Put("/users/:id/details", bind(dtos.AdminUpdateUserForm{}), AdminUpdateUser)
r.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword)
r.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions)
r.Delete("/users/:id", AdminDeleteUser)
}, reqGrafanaAdmin)
// rendering
+20 -3
View File
@@ -8,7 +8,7 @@ import (
)
func GetApiKeys(c *middleware.Context) {
query := m.GetApiKeysQuery{AccountId: c.AccountId}
query := m.GetApiKeysQuery{OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
c.JsonApiErr(500, "Failed to list api keys", err)
@@ -29,7 +29,7 @@ func GetApiKeys(c *middleware.Context) {
func DeleteApiKey(c *middleware.Context) {
id := c.ParamsInt64(":id")
cmd := &m.DeleteApiKeyCommand{Id: id, AccountId: c.AccountId}
cmd := &m.DeleteApiKeyCommand{Id: id, OrgId: c.OrgId}
err := bus.Dispatch(cmd)
if err != nil {
@@ -46,7 +46,7 @@ func AddApiKey(c *middleware.Context, cmd m.AddApiKeyCommand) {
return
}
cmd.AccountId = c.AccountId
cmd.OrgId = c.OrgId
cmd.Key = util.GetRandomString(64)
if err := bus.Dispatch(&cmd); err != nil {
@@ -62,3 +62,20 @@ func AddApiKey(c *middleware.Context, cmd m.AddApiKeyCommand) {
c.JSON(200, result)
}
func UpdateApiKey(c *middleware.Context, cmd m.UpdateApiKeyCommand) {
if !cmd.Role.IsValid() {
c.JsonApiErr(400, "Invalid role specified", nil)
return
}
cmd.OrgId = c.OrgId
err := bus.Dispatch(&cmd)
if err != nil {
c.JsonApiErr(500, "Failed to update api key", err)
return
}
c.JsonOK("API key updated")
}
+4 -4
View File
@@ -29,7 +29,7 @@ func isDasboardStarredByUser(c *middleware.Context, dashId int64) (bool, error)
func GetDashboard(c *middleware.Context) {
slug := c.Params(":slug")
query := m.GetDashboardQuery{Slug: slug, AccountId: c.AccountId}
query := m.GetDashboardQuery{Slug: slug, OrgId: c.OrgId}
err := bus.Dispatch(&query)
if err != nil {
c.JsonApiErr(404, "Dashboard not found", nil)
@@ -54,13 +54,13 @@ func GetDashboard(c *middleware.Context) {
func DeleteDashboard(c *middleware.Context) {
slug := c.Params(":slug")
query := m.GetDashboardQuery{Slug: slug, AccountId: c.AccountId}
query := m.GetDashboardQuery{Slug: slug, OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
c.JsonApiErr(404, "Dashboard not found", nil)
return
}
cmd := m.DeleteDashboardCommand{Slug: slug, AccountId: c.AccountId}
cmd := m.DeleteDashboardCommand{Slug: slug, OrgId: c.OrgId}
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "Failed to delete dashboard", err)
return
@@ -72,7 +72,7 @@ func DeleteDashboard(c *middleware.Context) {
}
func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) {
cmd.AccountId = c.AccountId
cmd.OrgId = c.OrgId
err := bus.Dispatch(&cmd)
if err != nil {
+9 -2
View File
@@ -17,6 +17,7 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string) *httputil.ReverseProxy
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.Host = target.Host
reqQueryVals := req.URL.Query()
@@ -25,6 +26,12 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string) *httputil.ReverseProxy
reqQueryVals.Add("u", ds.User)
reqQueryVals.Add("p", ds.Password)
req.URL.RawQuery = reqQueryVals.Encode()
} else if ds.Type == m.DS_INFLUXDB_08 {
req.URL.Path = util.JoinUrlFragments(target.Path, proxyPath)
reqQueryVals.Add("db", ds.Database)
reqQueryVals.Add("u", ds.User)
reqQueryVals.Add("p", ds.Password)
req.URL.RawQuery = reqQueryVals.Encode()
} else {
req.URL.Path = util.JoinUrlFragments(target.Path, proxyPath)
}
@@ -38,8 +45,8 @@ func ProxyDataSourceRequest(c *middleware.Context) {
id := c.ParamsInt64(":id")
query := m.GetDataSourceByIdQuery{
Id: id,
AccountId: c.AccountId,
Id: id,
OrgId: c.OrgId,
}
err := bus.Dispatch(&query)
+1 -1
View File
@@ -10,7 +10,7 @@ import (
m "github.com/grafana/grafana/pkg/models"
)
func TestAccountDataAccess(t *testing.T) {
func TestDataSourceProxy(t *testing.T) {
Convey("When getting graphite datasource proxy", t, func() {
ds := m.DataSource{Url: "htttp://graphite:8080", Type: m.DS_GRAPHITE}
+34 -7
View File
@@ -8,10 +8,9 @@ import (
)
func GetDataSources(c *middleware.Context) {
query := m.GetDataSourcesQuery{AccountId: c.AccountId}
err := bus.Dispatch(&query)
query := m.GetDataSourcesQuery{OrgId: c.OrgId}
if err != nil {
if err := bus.Dispatch(&query); err != nil {
c.JsonApiErr(500, "Failed to query datasources", err)
return
}
@@ -20,7 +19,7 @@ func GetDataSources(c *middleware.Context) {
for i, ds := range query.Result {
result[i] = &dtos.DataSource{
Id: ds.Id,
AccountId: ds.AccountId,
OrgId: ds.OrgId,
Name: ds.Name,
Url: ds.Url,
Type: ds.Type,
@@ -36,6 +35,34 @@ func GetDataSources(c *middleware.Context) {
c.JSON(200, result)
}
func GetDataSourceById(c *middleware.Context) {
query := m.GetDataSourceByIdQuery{
Id: c.ParamsInt64(":id"),
OrgId: c.OrgId,
}
if err := bus.Dispatch(&query); err != nil {
c.JsonApiErr(500, "Failed to query datasources", err)
return
}
ds := query.Result
c.JSON(200, &dtos.DataSource{
Id: ds.Id,
OrgId: ds.OrgId,
Name: ds.Name,
Url: ds.Url,
Type: ds.Type,
Access: ds.Access,
Password: ds.Password,
Database: ds.Database,
User: ds.User,
BasicAuth: ds.BasicAuth,
IsDefault: ds.IsDefault,
})
}
func DeleteDataSource(c *middleware.Context) {
id := c.ParamsInt64(":id")
@@ -44,7 +71,7 @@ func DeleteDataSource(c *middleware.Context) {
return
}
cmd := &m.DeleteDataSourceCommand{Id: id, AccountId: c.AccountId}
cmd := &m.DeleteDataSourceCommand{Id: id, OrgId: c.OrgId}
err := bus.Dispatch(cmd)
if err != nil {
@@ -63,7 +90,7 @@ func AddDataSource(c *middleware.Context) {
return
}
cmd.AccountId = c.AccountId
cmd.OrgId = c.OrgId
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "Failed to add datasource", err)
@@ -81,7 +108,7 @@ func UpdateDataSource(c *middleware.Context) {
return
}
cmd.AccountId = c.AccountId
cmd.OrgId = c.OrgId
err := bus.Dispatch(&cmd)
if err != nil {
+3 -3
View File
@@ -19,8 +19,8 @@ type CurrentUser struct {
Login string `json:"login"`
Email string `json:"email"`
Name string `json:"name"`
AccountRole m.RoleType `json:"accountRole"`
AccountName string `json:"acountName"`
OrgRole m.RoleType `json:"orgRole"`
OrgName string `json:"orgName"`
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
GravatarUrl string `json:"gravatarUrl"`
}
@@ -38,7 +38,7 @@ type Dashboard struct {
type DataSource struct {
Id int64 `json:"id"`
AccountId int64 `json:"accountId"`
OrgId int64 `json:"orgId"`
Name string `json:"name"`
Type m.DsType `json:"type"`
Access m.DsAccess `json:"access"`
+22
View File
@@ -0,0 +1,22 @@
package dtos
type AdminCreateUserForm struct {
Email string `json:"email"`
Login string `json:"login"`
Name string `json:"name"`
Password string `json:"password" binding:"Required"`
}
type AdminUpdateUserForm struct {
Email string `json:"email"`
Login string `json:"login"`
Name string `json:"name"`
}
type AdminUpdateUserPasswordForm struct {
Password string `json:"password" binding:"Required"`
}
type AdminUpdateUserPermissionsForm struct {
IsGrafanaAdmin bool `json:"IsGrafanaAdmin" binding:"Required"`
}
+25 -6
View File
@@ -9,23 +9,23 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
func getFrontendSettings(c *middleware.Context) (map[string]interface{}, error) {
accountDataSources := make([]*m.DataSource, 0)
func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, error) {
orgDataSources := make([]*m.DataSource, 0)
if c.IsSignedIn {
query := m.GetDataSourcesQuery{AccountId: c.AccountId}
query := m.GetDataSourcesQuery{OrgId: c.OrgId}
err := bus.Dispatch(&query)
if err != nil {
return nil, err
}
accountDataSources = query.Result
orgDataSources = query.Result
}
datasources := make(map[string]interface{})
for _, ds := range accountDataSources {
for _, ds := range orgDataSources {
url := ds.Url
if ds.Access == m.DS_ACCESS_PROXY {
@@ -38,7 +38,7 @@ func getFrontendSettings(c *middleware.Context) (map[string]interface{}, error)
"default": ds.IsDefault,
}
if ds.Type == m.DS_INFLUXDB {
if ds.Type == m.DS_INFLUXDB_08 {
if ds.Access == m.DS_ACCESS_DIRECT {
dsMap["username"] = ds.User
dsMap["password"] = ds.Password
@@ -46,6 +46,15 @@ func getFrontendSettings(c *middleware.Context) (map[string]interface{}, error)
}
}
if ds.Type == m.DS_INFLUXDB {
if ds.Access == m.DS_ACCESS_DIRECT {
dsMap["username"] = ds.User
dsMap["password"] = ds.Password
dsMap["database"] = ds.Database
dsMap["url"] = url
}
}
if ds.Type == m.DS_ES {
dsMap["index"] = ds.Database
}
@@ -71,3 +80,13 @@ func getFrontendSettings(c *middleware.Context) (map[string]interface{}, error)
return jsonObj, nil
}
func GetFrontendSettings(c *middleware.Context) {
settings, err := getFrontendSettingsMap(c)
if err != nil {
c.JsonApiErr(400, "Failed to get frontend settings", err)
return
}
c.JSON(200, settings)
}
+3 -3
View File
@@ -7,7 +7,7 @@ import (
)
func setIndexViewData(c *middleware.Context) error {
settings, err := getFrontendSettings(c)
settings, err := getFrontendSettingsMap(c)
if err != nil {
return err
}
@@ -17,8 +17,8 @@ func setIndexViewData(c *middleware.Context) error {
Login: c.Login,
Email: c.Email,
Name: c.Name,
AccountName: c.AccountName,
AccountRole: c.AccountRole,
OrgName: c.OrgName,
OrgRole: c.OrgRole,
GravatarUrl: dtos.GetGravatarUrl(c.Email),
IsGrafanaAdmin: c.IsGrafanaAdmin,
}
+50
View File
@@ -0,0 +1,50 @@
package api
import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
)
func GetOrg(c *middleware.Context) {
query := m.GetOrgByIdQuery{Id: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrOrgNotFound {
c.JsonApiErr(404, "Organization not found", err)
return
}
c.JsonApiErr(500, "Failed to get organization", err)
return
}
org := m.OrgDTO{
Id: query.Result.Id,
Name: query.Result.Name,
}
c.JSON(200, &org)
}
func CreateOrg(c *middleware.Context, cmd m.CreateOrgCommand) {
cmd.UserId = c.UserId
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "Failed to create organization", err)
return
}
c.JsonOK("Organization created")
}
func UpdateOrg(c *middleware.Context, cmd m.UpdateOrgCommand) {
cmd.OrgId = c.OrgId
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "Failed to update organization", err)
return
}
c.JsonOK("Organization updated")
}
@@ -6,7 +6,7 @@ import (
m "github.com/grafana/grafana/pkg/models"
)
func AddAccountUser(c *middleware.Context, cmd m.AddAccountUserCommand) {
func AddOrgUser(c *middleware.Context, cmd m.AddOrgUserCommand) {
if !cmd.Role.IsValid() {
c.JsonApiErr(400, "Invalid role specified", nil)
return
@@ -26,19 +26,19 @@ func AddAccountUser(c *middleware.Context, cmd m.AddAccountUserCommand) {
return
}
cmd.AccountId = c.AccountId
cmd.OrgId = c.OrgId
cmd.UserId = userToAdd.Id
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "Could not add user to account", err)
c.JsonApiErr(500, "Could not add user to organization", err)
return
}
c.JsonOK("User added to account")
c.JsonOK("User added to organization")
}
func GetAccountUsers(c *middleware.Context) {
query := m.GetAccountUsersQuery{AccountId: c.AccountId}
func GetOrgUsers(c *middleware.Context) {
query := m.GetOrgUsersQuery{OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
c.JsonApiErr(500, "Failed to get account user", err)
@@ -48,18 +48,18 @@ func GetAccountUsers(c *middleware.Context) {
c.JSON(200, query.Result)
}
func RemoveAccountUser(c *middleware.Context) {
func RemoveOrgUser(c *middleware.Context) {
userId := c.ParamsInt64(":id")
cmd := m.RemoveAccountUserCommand{AccountId: c.AccountId, UserId: userId}
cmd := m.RemoveOrgUserCommand{OrgId: c.OrgId, UserId: userId}
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrLastAccountAdmin {
c.JsonApiErr(400, "Cannot remove last account admin", nil)
if err == m.ErrLastOrgAdmin {
c.JsonApiErr(400, "Cannot remove last organization admin", nil)
return
}
c.JsonApiErr(500, "Failed to remove user from account", err)
c.JsonApiErr(500, "Failed to remove user from organization", err)
}
c.JsonOK("User removed from account")
c.JsonOK("User removed from organization")
}
+2 -6
View File
@@ -4,7 +4,6 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
)
// TODO: this needs to be cached or improved somehow
@@ -45,7 +44,7 @@ func Search(c *middleware.Context) {
if tagcloud == "true" {
query := m.GetDashboardTagsQuery{AccountId: c.AccountId}
query := m.GetDashboardTagsQuery{OrgId: c.OrgId}
err := bus.Dispatch(&query)
if err != nil {
c.JsonApiErr(500, "Failed to get tags from database", err)
@@ -61,7 +60,7 @@ func Search(c *middleware.Context) {
UserId: c.UserId,
Limit: limit,
IsStarred: starred == "true",
AccountId: c.AccountId,
OrgId: c.OrgId,
}
err := bus.Dispatch(&query)
@@ -76,9 +75,6 @@ func Search(c *middleware.Context) {
}
result.Dashboards = query.Result
for _, dash := range result.Dashboards {
dash.Url = setting.ToAbsUrl("dashboard/db/" + dash.Slug)
}
}
c.JSON(200, result)
+51 -20
View File
@@ -4,13 +4,14 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/util"
)
func GetUser(c *middleware.Context) {
query := m.GetUserInfoQuery{UserId: c.UserId}
if err := bus.Dispatch(&query); err != nil {
c.JsonApiErr(500, "Failed to get account", err)
c.JsonApiErr(500, "Failed to get user", err)
return
}
@@ -21,23 +22,23 @@ func UpdateUser(c *middleware.Context, cmd m.UpdateUserCommand) {
cmd.UserId = c.UserId
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(400, "Failed to update account", err)
c.JsonApiErr(400, "Failed to update user", err)
return
}
c.JsonOK("Account updated")
c.JsonOK("User updated")
}
func GetUserAccounts(c *middleware.Context) {
query := m.GetUserAccountsQuery{UserId: c.UserId}
func GetUserOrgList(c *middleware.Context) {
query := m.GetUserOrgListQuery{UserId: c.UserId}
if err := bus.Dispatch(&query); err != nil {
c.JsonApiErr(500, "Failed to get user accounts", err)
c.JsonApiErr(500, "Failed to get user organizations", err)
return
}
for _, ac := range query.Result {
if ac.AccountId == c.AccountId {
if ac.OrgId == c.OrgId {
ac.IsUsing = true
break
}
@@ -46,17 +47,17 @@ func GetUserAccounts(c *middleware.Context) {
c.JSON(200, query.Result)
}
func validateUsingAccount(userId int64, accountId int64) bool {
query := m.GetUserAccountsQuery{UserId: userId}
func validateUsingOrg(userId int64, orgId int64) bool {
query := m.GetUserOrgListQuery{UserId: userId}
if err := bus.Dispatch(&query); err != nil {
return false
}
// validate that the account id in the list
// validate that the org id in the list
valid := false
for _, other := range query.Result {
if other.AccountId == accountId {
if other.OrgId == orgId {
valid = true
}
}
@@ -64,23 +65,53 @@ func validateUsingAccount(userId int64, accountId int64) bool {
return valid
}
func SetUsingAccount(c *middleware.Context) {
usingAccountId := c.ParamsInt64(":id")
func UserSetUsingOrg(c *middleware.Context) {
orgId := c.ParamsInt64(":id")
if !validateUsingAccount(c.UserId, usingAccountId) {
c.JsonApiErr(401, "Not a valid account", nil)
if !validateUsingOrg(c.UserId, orgId) {
c.JsonApiErr(401, "Not a valid organization", nil)
return
}
cmd := m.SetUsingAccountCommand{
UserId: c.UserId,
AccountId: usingAccountId,
cmd := m.SetUsingOrgCommand{
UserId: c.UserId,
OrgId: orgId,
}
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "Failed change active account", err)
c.JsonApiErr(500, "Failed change active organization", err)
return
}
c.JsonOK("Active account changed")
c.JsonOK("Active organization changed")
}
func ChangeUserPassword(c *middleware.Context, cmd m.ChangeUserPasswordCommand) {
userQuery := m.GetUserByIdQuery{Id: c.UserId}
if err := bus.Dispatch(&userQuery); err != nil {
c.JsonApiErr(500, "Could not read user from database", err)
return
}
passwordHashed := util.EncodePassword(cmd.OldPassword, userQuery.Result.Salt)
if passwordHashed != userQuery.Result.Password {
c.JsonApiErr(401, "Invalid old password", nil)
return
}
if len(cmd.NewPassword) < 4 {
c.JsonApiErr(400, "New password too short", nil)
return
}
cmd.UserId = c.UserId
cmd.NewPassword = util.EncodePassword(cmd.NewPassword, userQuery.Result.Salt)
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "Failed to change user password", err)
return
}
c.JsonOK("User password changed")
}
+13
View File
@@ -0,0 +1,13 @@
package cmd
import (
"github.com/codegangsta/cli"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
)
func initRuntime(c *cli.Context) {
setting.NewConfigContext(c.GlobalString("config"))
sqlstore.NewEngine()
sqlstore.EnsureAdminUser()
}
+104
View File
@@ -0,0 +1,104 @@
package cmd
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"github.com/codegangsta/cli"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
)
var ImportDashboard = cli.Command{
Name: "dashboards:import",
Usage: "imports dashboards in JSON from a directory",
Description: "Starts Grafana import process",
Action: runImport,
Flags: []cli.Flag{
cli.StringFlag{
Name: "dir",
Usage: "path to folder containing json dashboards",
},
},
}
func runImport(c *cli.Context) {
dir := c.String("dir")
if len(dir) == 0 {
log.ConsoleFatalf("Missing command flag --dir")
}
file, err := os.Stat(dir)
if os.IsNotExist(err) {
log.ConsoleFatalf("Directory does not exist: %v", dir)
}
if !file.IsDir() {
log.ConsoleFatalf("%v is not a directory", dir)
}
if !c.Args().Present() {
log.ConsoleFatal("Organization name arg is required")
}
orgName := c.Args().First()
initRuntime(c)
orgQuery := m.GetOrgByNameQuery{Name: orgName}
if err := bus.Dispatch(&orgQuery); err != nil {
log.ConsoleFatalf("Failed to find account", err)
}
orgId := orgQuery.Result.Id
visitor := func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if f.IsDir() {
return nil
}
if strings.HasSuffix(f.Name(), ".json") {
if err := importDashboard(path, orgId); err != nil {
log.ConsoleFatalf("Failed to import dashboard file: %v, err: %v", path, err)
}
}
return nil
}
if err := filepath.Walk(dir, visitor); err != nil {
log.ConsoleFatalf("Failed to scan dir for json files: %v", err)
}
}
func importDashboard(path string, orgId int64) error {
log.ConsoleInfof("Importing %v", path)
reader, err := os.Open(path)
if err != nil {
return err
}
dash := m.NewDashboard("temp")
jsonParser := json.NewDecoder(reader)
if err := jsonParser.Decode(&dash.Data); err != nil {
return err
}
dash.Data["id"] = nil
cmd := m.SaveDashboardCommand{
OrgId: orgId,
Dashboard: dash.Data,
}
if err := bus.Dispatch(&cmd); err != nil {
return err
}
return nil
}
+230
View File
@@ -0,0 +1,230 @@
package cmd
import (
"fmt"
"os"
"text/tabwriter"
"github.com/codegangsta/cli"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
)
var (
ListDataSources = cli.Command{
Name: "datasources",
Usage: "list datasources",
Description: "Lists the datasources in the system",
Action: listDatasources,
}
CreateDataSource = cli.Command{
Name: "datasources:create",
Usage: "creates a new datasource",
Description: "Creates a new datasource",
Action: createDataSource,
Flags: []cli.Flag{
cli.StringFlag{
Name: "type",
Value: "graphite",
Usage: fmt.Sprintf("Datasource type [%s,%s,%s,%s]",
m.DS_GRAPHITE, m.DS_INFLUXDB, m.DS_ES, m.DS_OPENTSDB),
},
cli.StringFlag{
Name: "access",
Value: "proxy",
Usage: "Datasource access [proxy,direct]",
},
cli.BoolFlag{
Name: "default",
Usage: "Make this the default datasource",
},
cli.StringFlag{
Name: "db",
Usage: "InfluxDB DB",
},
cli.StringFlag{
Name: "user",
Usage: "InfluxDB username",
},
cli.StringFlag{
Name: "password",
Usage: "InfluxDB password",
},
},
}
DescribeDataSource = cli.Command{
Name: "datasources:info",
Usage: "describe the details of a datasource",
Description: "Describes the details of a datasource",
Action: describeDataSource,
}
DeleteDataSource = cli.Command{
Name: "datasources:delete",
Usage: "Deletes a datasource",
Description: "Deletes a datasource",
Action: deleteDataSource,
}
)
func createDataSource(c *cli.Context) {
initRuntime(c)
if len(c.Args()) != 3 {
log.ConsoleFatal("Missing required arguments")
}
name := c.Args().First()
ds := c.Args()[1]
url := c.Args()[2]
dsType := c.String("type")
dsAccess := c.String("access")
dsDefault := c.Bool("default")
orgQuery := m.GetOrgByNameQuery{Name: name}
if err := bus.Dispatch(&orgQuery); err != nil {
log.ConsoleFatalf("Failed to find organization: %s", err)
}
orgId := orgQuery.Result.Id
query := m.GetDataSourceByNameQuery{OrgId: orgId, Name: ds}
if err := bus.Dispatch(&query); err != nil {
if err != m.ErrDataSourceNotFound {
log.ConsoleFatalf("Failed to query for existing datasource: %s", err)
}
}
if query.Result.Id > 0 {
log.ConsoleFatalf("DataSource %s already exists", ds)
}
cmd := m.AddDataSourceCommand{
OrgId: orgId,
Name: ds,
Url: url,
Type: m.DsType(dsType),
Access: m.DsAccess(dsAccess),
IsDefault: dsDefault,
}
switch dsType {
case m.DS_INFLUXDB:
db := c.String("db")
if db == "" {
log.ConsoleFatal("db name is required for influxdb datasources")
}
cmd.Database = db
cmd.User = c.String("user")
cmd.Password = c.String("password")
}
if err := bus.Dispatch(&cmd); err != nil {
log.ConsoleFatalf("Failed to create datasource: %s", err)
}
datasource := cmd.Result
log.ConsoleInfof("Datasource %s created", datasource.Name)
}
func listDatasources(c *cli.Context) {
initRuntime(c)
if !c.Args().Present() {
log.ConsoleFatal("Account name arg is required")
}
name := c.Args().First()
orgQuery := m.GetOrgByNameQuery{Name: name}
if err := bus.Dispatch(&orgQuery); err != nil {
log.ConsoleFatalf("Failed to find organization: %s", err)
}
orgId := orgQuery.Result.Id
query := m.GetDataSourcesQuery{OrgId: orgId}
if err := bus.Dispatch(&query); err != nil {
log.ConsoleFatalf("Failed to find datasources: %s", err)
}
w := tabwriter.NewWriter(os.Stdout, 8, 1, 4, ' ', 0)
fmt.Fprintf(w, "ID\tNAME\tURL\tTYPE\tACCESS\tDEFAULT\n")
for _, ds := range query.Result {
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%t\n", ds.Id, ds.Name, ds.Url, ds.Type,
ds.Access, ds.IsDefault)
}
w.Flush()
}
func describeDataSource(c *cli.Context) {
initRuntime(c)
if len(c.Args()) != 2 {
log.ConsoleFatal("Organization and datasource name args are required")
}
name := c.Args().First()
ds := c.Args()[1]
orgQuery := m.GetOrgByNameQuery{Name: name}
if err := bus.Dispatch(&orgQuery); err != nil {
log.ConsoleFatalf("Failed to find organization: %s", err)
}
orgId := orgQuery.Result.Id
query := m.GetDataSourceByNameQuery{OrgId: orgId, Name: ds}
if err := bus.Dispatch(&query); err != nil {
log.ConsoleFatalf("Failed to find datasource: %s", err)
}
datasource := query.Result
w := tabwriter.NewWriter(os.Stdout, 8, 1, 4, ' ', 0)
fmt.Fprintf(w, "NAME\t%s\n", datasource.Name)
fmt.Fprintf(w, "URL\t%s\n", datasource.Url)
fmt.Fprintf(w, "DEFAULT\t%t\n", datasource.IsDefault)
fmt.Fprintf(w, "ACCESS\t%s\n", datasource.Access)
fmt.Fprintf(w, "TYPE\t%s\n", datasource.Type)
switch datasource.Type {
case m.DS_INFLUXDB:
fmt.Fprintf(w, "DATABASE\t%s\n", datasource.Database)
fmt.Fprintf(w, "DB USER\t%s\n", datasource.User)
fmt.Fprintf(w, "DB PASSWORD\t%s\n", datasource.Password)
case m.DS_ES:
fmt.Fprintf(w, "INDEX\t%s\n", datasource.Database)
}
w.Flush()
}
func deleteDataSource(c *cli.Context) {
initRuntime(c)
if len(c.Args()) != 2 {
log.ConsoleFatal("Account and datasource name args are required")
}
name := c.Args().First()
ds := c.Args()[1]
orgQuery := m.GetOrgByNameQuery{Name: name}
if err := bus.Dispatch(&orgQuery); err != nil {
log.ConsoleFatalf("Failed to find organization: %s", err)
}
orgId := orgQuery.Result.Id
query := m.GetDataSourceByNameQuery{OrgId: orgId, Name: ds}
if err := bus.Dispatch(&query); err != nil {
log.ConsoleFatalf("Failed to find datasource: %s", err)
}
datasource := query.Result
cmd := m.DeleteDataSourceCommand{OrgId: orgId, Id: datasource.Id}
if err := bus.Dispatch(&cmd); err != nil {
log.ConsoleFatalf("Failed to delete datasource: %s", err)
}
log.ConsoleInfof("DataSource %s deleted", ds)
}
-120
View File
@@ -1,120 +0,0 @@
package cmd
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"github.com/codegangsta/cli"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
)
var CmdImportJson = cli.Command{
Name: "import-json",
Usage: "grafana import",
Description: "Starts Grafana import process",
Action: runImport,
Flags: []cli.Flag{
cli.StringFlag{
Name: "dir",
Usage: "path to folder containing json dashboards",
},
cli.StringFlag{
Name: "account",
Usage: "Account name to save dashboards under",
},
cli.StringFlag{
Name: "config",
Value: "grafana.ini",
Usage: "path to config file",
},
},
}
func runImport(c *cli.Context) {
dir := c.String("dir")
if len(dir) == 0 {
log.Error(3, "Missing command flag --dir")
return
}
file, err := os.Stat(dir)
if os.IsNotExist(err) {
log.Error(3, "Directory does not exist: %v", dir)
return
}
if !file.IsDir() {
log.Error(3, "%v is not a directory", dir)
return
}
accountName := c.String("account")
if len(accountName) == 0 {
log.Error(3, "Missing command flag --account")
return
}
setting.NewConfigContext()
sqlstore.NewEngine()
sqlstore.EnsureAdminUser()
accountQuery := m.GetAccountByNameQuery{Name: accountName}
if err := bus.Dispatch(&accountQuery); err != nil {
log.Error(3, "Failed to find account", err)
return
}
accountId := accountQuery.Result.Id
visitor := func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if f.IsDir() {
return nil
}
if strings.HasSuffix(f.Name(), ".json") {
if err := importDashboard(path, accountId); err != nil {
log.Error(3, "Failed to import dashboard file: %v, err: %v", path, err)
}
}
return nil
}
if err := filepath.Walk(dir, visitor); err != nil {
log.Error(3, "failed to scan dir for json files: %v", err)
}
}
func importDashboard(path string, accountId int64) error {
log.Info("Importing %v", path)
reader, err := os.Open(path)
if err != nil {
return err
}
dash := m.NewDashboard("temp")
jsonParser := json.NewDecoder(reader)
if err := jsonParser.Decode(&dash.Data); err != nil {
return err
}
cmd := m.SaveDashboardCommand{
AccountId: accountId,
Dashboard: dash.Data,
}
if err := bus.Dispatch(&cmd); err != nil {
return err
}
return nil
}
+99
View File
@@ -0,0 +1,99 @@
package cmd
import (
"fmt"
"os"
"text/tabwriter"
"github.com/codegangsta/cli"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
)
var ListOrgs = cli.Command{
Name: "orgs",
Usage: "list organizations",
Description: "Lists the organizations in the system",
Action: listOrgs,
}
var CreateOrg = cli.Command{
Name: "orgs:create",
Usage: "Creates a new organization",
Description: "Creates a new organization",
Action: createOrg,
}
var DeleteOrg = cli.Command{
Name: "orgs:delete",
Usage: "Delete an existing organization",
Description: "Deletes an existing organization",
Action: deleteOrg,
}
func listOrgs(c *cli.Context) {
initRuntime(c)
orgsQuery := m.GetOrgListQuery{}
if err := bus.Dispatch(&orgsQuery); err != nil {
log.ConsoleFatalf("Failed to find organizations: %s", err)
}
w := tabwriter.NewWriter(os.Stdout, 8, 1, 4, ' ', 0)
fmt.Fprintf(w, "ID\tNAME\n")
for _, org := range orgsQuery.Result {
fmt.Fprintf(w, "%d\t%s\n", org.Id, org.Name)
}
w.Flush()
}
func createOrg(c *cli.Context) {
initRuntime(c)
if !c.Args().Present() {
log.ConsoleFatal("Organization name arg is required")
}
name := c.Args().First()
adminQuery := m.GetUserByLoginQuery{LoginOrEmail: setting.AdminUser}
if err := bus.Dispatch(&adminQuery); err == m.ErrUserNotFound {
log.ConsoleFatalf("Failed to find default admin user: %s", err)
}
adminUser := adminQuery.Result
cmd := m.CreateOrgCommand{Name: name, UserId: adminUser.Id}
if err := bus.Dispatch(&cmd); err != nil {
log.ConsoleFatalf("Failed to create organization: %s", err)
}
log.ConsoleInfof("Organization %s created for admin user %s\n", name, adminUser.Email)
}
func deleteOrg(c *cli.Context) {
initRuntime(c)
if !c.Args().Present() {
log.ConsoleFatal("Organization name arg is required")
}
name := c.Args().First()
orgQuery := m.GetOrgByNameQuery{Name: name}
if err := bus.Dispatch(&orgQuery); err != nil {
log.ConsoleFatalf("Failed to find organization: %s", err)
}
orgId := orgQuery.Result.Id
cmd := m.DeleteOrgCommand{Id: orgId}
if err := bus.Dispatch(&cmd); err != nil {
log.ConsoleFatalf("Failed to delete organization: %s", err)
}
log.ConsoleInfof("Organization %s deleted", name)
}
+4 -13
View File
@@ -17,23 +17,15 @@ import (
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/services/eventpublisher"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/social"
)
var CmdWeb = cli.Command{
var Web = cli.Command{
Name: "web",
Usage: "grafana web",
Usage: "Starts Grafana backend & web server",
Description: "Starts Grafana backend & web server",
Action: runWeb,
Flags: []cli.Flag{
cli.StringFlag{
Name: "config",
Value: "grafana.ini",
Usage: "path to config file",
},
},
}
func newMacaron() *macaron.Macaron {
@@ -78,10 +70,9 @@ func runWeb(c *cli.Context) {
log.Info("Starting Grafana")
log.Info("Version: %v, Commit: %v, Build date: %v", setting.BuildVersion, setting.BuildCommit, time.Unix(setting.BuildStamp, 0))
setting.NewConfigContext()
initRuntime(c)
social.NewOAuthService()
sqlstore.NewEngine()
sqlstore.EnsureAdminUser()
eventpublisher.Init()
var err error
+2 -2
View File
@@ -50,13 +50,13 @@ func ToOnWriteEvent(event interface{}) (*OnTheWireEvent, error) {
return &wireEvent, nil
}
type AccountCreated struct {
type OrgCreated struct {
Timestamp time.Time `json:"timestamp"`
Id int64 `json:"id"`
Name string `json:"name"`
}
type AccountUpdated struct {
type OrgUpdated struct {
Timestamp time.Time `json:"timestamp"`
Id int64 `json:"id"`
Name string `json:"name"`
+92 -10
View File
@@ -6,6 +6,7 @@ package log
import (
"encoding/json"
"fmt"
"log"
"os"
"runtime"
@@ -21,15 +22,26 @@ func NewBrush(color string) Brush {
}
}
var colors = []Brush{
NewBrush("1;36"), // Trace cyan
NewBrush("1;34"), // Debug blue
NewBrush("1;32"), // Info green
NewBrush("1;33"), // Warn yellow
NewBrush("1;31"), // Error red
NewBrush("1;35"), // Critical purple
NewBrush("1;31"), // Fatal red
}
var (
Red = NewBrush("1;31")
Purple = NewBrush("1;35")
Yellow = NewBrush("1;33")
Green = NewBrush("1;32")
Blue = NewBrush("1;34")
Cyan = NewBrush("1;36")
colors = []Brush{
Cyan, // Trace cyan
Blue, // Debug blue
Green, // Info green
Yellow, // Warn yellow
Red, // Error red
Purple, // Critical purple
Red, // Fatal red
}
consoleWriter = &ConsoleWriter{lg: log.New(os.Stdout, "", 0),
Level: TRACE}
)
// ConsoleWriter implements LoggerInterface and writes messages to terminal.
type ConsoleWriter struct {
@@ -40,7 +52,7 @@ type ConsoleWriter struct {
// create ConsoleWriter returning as LoggerInterface.
func NewConsole() LoggerInterface {
return &ConsoleWriter{
lg: log.New(os.Stdout, "", log.Ldate|log.Ltime),
lg: log.New(os.Stderr, "", log.Ldate|log.Ltime),
Level: TRACE,
}
}
@@ -68,6 +80,76 @@ func (_ *ConsoleWriter) Flush() {
func (_ *ConsoleWriter) Destroy() {
}
func printConsole(level int, msg string) {
consoleWriter.WriteMsg(msg, 0, level)
}
func printfConsole(level int, format string, v ...interface{}) {
consoleWriter.WriteMsg(fmt.Sprintf(format, v...), 0, level)
}
// ConsoleTrace prints to stdout using TRACE colors
func ConsoleTrace(s string) {
printConsole(TRACE, s)
}
// ConsoleTracef prints a formatted string to stdout using TRACE colors
func ConsoleTracef(format string, v ...interface{}) {
printfConsole(TRACE, format, v...)
}
// ConsoleDebug prints to stdout using DEBUG colors
func ConsoleDebug(s string) {
printConsole(DEBUG, s)
}
// ConsoleDebugf prints a formatted string to stdout using DEBUG colors
func ConsoleDebugf(format string, v ...interface{}) {
printfConsole(DEBUG, format, v...)
}
// ConsoleInfo prints to stdout using INFO colors
func ConsoleInfo(s string) {
printConsole(INFO, s)
}
// ConsoleInfof prints a formatted string to stdout using INFO colors
func ConsoleInfof(format string, v ...interface{}) {
printfConsole(INFO, format, v...)
}
// ConsoleWarn prints to stdout using WARN colors
func ConsoleWarn(s string) {
printConsole(WARN, s)
}
// ConsoleWarnf prints a formatted string to stdout using WARN colors
func ConsoleWarnf(format string, v ...interface{}) {
printfConsole(WARN, format, v...)
}
// ConsoleError prints to stdout using ERROR colors
func ConsoleError(s string) {
printConsole(ERROR, s)
}
// ConsoleErrorf prints a formatted string to stdout using ERROR colors
func ConsoleErrorf(format string, v ...interface{}) {
printfConsole(ERROR, format, v...)
}
// ConsoleFatal prints to stdout using FATAL colors
func ConsoleFatal(s string) {
printConsole(FATAL, s)
os.Exit(1)
}
// ConsoleFatalf prints a formatted string to stdout using FATAL colors
func ConsoleFatalf(format string, v ...interface{}) {
printfConsole(FATAL, format, v...)
os.Exit(1)
}
func init() {
Register("console", NewConsole)
}
+1 -1
View File
@@ -56,7 +56,7 @@ func RoleAuth(roles ...m.RoleType) macaron.Handler {
return func(c *Context) {
ok := false
for _, role := range roles {
if role == c.AccountRole {
if role == c.OrgRole {
ok = true
break
}
+9 -8
View File
@@ -56,22 +56,23 @@ func GetContextHandler() macaron.Handler {
ctx.SignedInUser = &m.SignedInUser{}
// TODO: fix this
ctx.AccountRole = keyInfo.Role
ctx.OrgRole = keyInfo.Role
ctx.ApiKeyId = keyInfo.Id
ctx.AccountId = keyInfo.AccountId
ctx.OrgId = keyInfo.OrgId
}
} else if setting.AnonymousEnabled {
accountQuery := m.GetAccountByNameQuery{Name: setting.AnonymousAccountName}
if err := bus.Dispatch(&accountQuery); err != nil {
if err == m.ErrAccountNotFound {
log.Error(3, "Anonymous access account name does not exist", nil)
orgQuery := m.GetOrgByNameQuery{Name: setting.AnonymousOrgName}
if err := bus.Dispatch(&orgQuery); err != nil {
if err == m.ErrOrgNotFound {
log.Error(3, "Anonymous access organization name does not exist", nil)
}
} else {
ctx.IsSignedIn = false
ctx.HasAnonymousAccess = true
ctx.SignedInUser = &m.SignedInUser{}
ctx.AccountRole = m.RoleType(setting.AnonymousAccountRole)
ctx.AccountId = accountQuery.Result.Id
ctx.OrgRole = m.RoleType(setting.AnonymousOrgRole)
ctx.OrgId = orgQuery.Result.Id
ctx.OrgName = orgQuery.Result.Name
}
}
-62
View File
@@ -1,62 +0,0 @@
package models
import (
"errors"
"time"
)
// Typed errors
var (
ErrAccountNotFound = errors.New("Account not found")
)
type Account struct {
Id int64
Version int
Name string
Created time.Time
Updated time.Time
}
// ---------------------
// COMMANDS
type CreateAccountCommand struct {
Name string `json:"name" binding:"Required"`
// initial admin user for account
UserId int64 `json:"-"`
Result Account `json:"-"`
}
type UpdateAccountCommand struct {
Name string `json:"name" binding:"Required"`
AccountId int64 `json:"-"`
}
type GetUserAccountsQuery struct {
UserId int64
Result []*UserAccountDTO
}
type GetAccountByIdQuery struct {
Id int64
Result *Account
}
type GetAccountByNameQuery struct {
Name string
Result *Account
}
type AccountDTO struct {
Id int64 `json:"id"`
Name string `json:"name"`
}
type UserAccountDTO struct {
AccountId int64 `json:"accountId"`
Name string `json:"name"`
Role RoleType `json:"role"`
IsUsing bool `json:"isUsing"`
}
-67
View File
@@ -1,67 +0,0 @@
package models
import (
"errors"
"time"
)
// Typed errors
var (
ErrInvalidRoleType = errors.New("Invalid role type")
ErrLastAccountAdmin = errors.New("Cannot remove last account admin")
)
type RoleType string
const (
ROLE_VIEWER RoleType = "Viewer"
ROLE_EDITOR RoleType = "Editor"
ROLE_ADMIN RoleType = "Admin"
)
func (r RoleType) IsValid() bool {
return r == ROLE_VIEWER || r == ROLE_ADMIN || r == ROLE_EDITOR
}
type AccountUser struct {
AccountId int64
UserId int64
Role RoleType
Created time.Time
Updated time.Time
}
// ---------------------
// COMMANDS
type RemoveAccountUserCommand struct {
UserId int64
AccountId int64
}
type AddAccountUserCommand struct {
LoginOrEmail string `json:"loginOrEmail" binding:"Required"`
Role RoleType `json:"role" binding:"Required"`
AccountId int64 `json:"-"`
UserId int64 `json:"-"`
}
// ----------------------
// QUERIES
type GetAccountUsersQuery struct {
AccountId int64
Result []*AccountUserDTO
}
// ----------------------
// Projections and DTOs
type AccountUserDTO struct {
AccountId int64 `json:"accountId"`
UserId int64 `json:"userId"`
Email string `json:"email"`
Login string `json:"login"`
Role string `json:"role"`
}
+16 -16
View File
@@ -8,22 +8,22 @@ import (
var ErrInvalidApiKey = errors.New("Invalid API Key")
type ApiKey struct {
Id int64
AccountId int64
Name string
Key string
Role RoleType
Created time.Time
Updated time.Time
Id int64
OrgId int64
Name string
Key string
Role RoleType
Created time.Time
Updated time.Time
}
// ---------------------
// COMMANDS
type AddApiKeyCommand struct {
Name string `json:"name" binding:"Required"`
Role RoleType `json:"role" binding:"Required"`
AccountId int64 `json:"-"`
Key string `json:"-"`
Name string `json:"name" binding:"Required"`
Role RoleType `json:"role" binding:"Required"`
OrgId int64 `json:"-"`
Key string `json:"-"`
Result *ApiKey `json:"-"`
}
@@ -33,20 +33,20 @@ type UpdateApiKeyCommand struct {
Name string `json:"name"`
Role RoleType `json:"role"`
AccountId int64 `json:"-"`
OrgId int64 `json:"-"`
}
type DeleteApiKeyCommand struct {
Id int64 `json:"id"`
AccountId int64 `json:"-"`
Id int64 `json:"id"`
OrgId int64 `json:"-"`
}
// ----------------------
// QUERIES
type GetApiKeysQuery struct {
AccountId int64
Result []*ApiKey
OrgId int64
Result []*ApiKey
}
type GetApiKeyByKeyQuery struct {
+10 -10
View File
@@ -14,10 +14,10 @@ var (
)
type Dashboard struct {
Id int64
Slug string
AccountId int64
Version int
Id int64
Slug string
OrgId int64
Version int
Created time.Time
Updated time.Time
@@ -53,7 +53,7 @@ func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
dash := &Dashboard{}
dash.Data = cmd.Dashboard
dash.Title = dash.Data["title"].(string)
dash.AccountId = cmd.AccountId
dash.OrgId = cmd.OrgId
dash.UpdateSlug()
if dash.Data["id"] != nil {
@@ -80,14 +80,14 @@ func (dash *Dashboard) UpdateSlug() {
type SaveDashboardCommand struct {
Dashboard map[string]interface{} `json:"dashboard"`
AccountId int64 `json:"-"`
OrgId int64 `json:"-"`
Result *Dashboard
}
type DeleteDashboardCommand struct {
Slug string
AccountId int64
Slug string
OrgId int64
}
//
@@ -95,8 +95,8 @@ type DeleteDashboardCommand struct {
//
type GetDashboardQuery struct {
Slug string
AccountId int64
Slug string
OrgId int64
Result *Dashboard
}
+20 -12
View File
@@ -8,7 +8,9 @@ import (
const (
DS_GRAPHITE = "graphite"
DS_INFLUXDB = "influxdb"
DS_INFLUXDB_08 = "influxdb_08"
DS_ES = "elasticsearch"
DS_OPENTSDB = "opentsdb"
DS_ACCESS_DIRECT = "direct"
DS_ACCESS_PROXY = "proxy"
)
@@ -22,9 +24,9 @@ type DsType string
type DsAccess string
type DataSource struct {
Id int64
AccountId int64
Version int
Id int64
OrgId int64
Version int
Name string
Type DsType
@@ -47,7 +49,7 @@ type DataSource struct {
// Also acts as api DTO
type AddDataSourceCommand struct {
AccountId int64 `json:"-"`
OrgId int64 `json:"-"`
Name string
Type DsType
Access DsAccess
@@ -63,7 +65,7 @@ type AddDataSourceCommand struct {
// Also acts as api DTO
type UpdateDataSourceCommand struct {
Id int64
AccountId int64
OrgId int64
Name string
Type DsType
Access DsAccess
@@ -75,22 +77,28 @@ type UpdateDataSourceCommand struct {
}
type DeleteDataSourceCommand struct {
Id int64
AccountId int64
Id int64
OrgId int64
}
// ---------------------
// QUERIES
type GetDataSourcesQuery struct {
AccountId int64
Result []*DataSource
OrgId int64
Result []*DataSource
}
type GetDataSourceByIdQuery struct {
Id int64
AccountId int64
Result DataSource
Id int64
OrgId int64
Result DataSource
}
type GetDataSourceByNameQuery struct {
Name string
OrgId int64
Result DataSource
}
// ---------------------
+65
View File
@@ -0,0 +1,65 @@
package models
import (
"errors"
"time"
)
// Typed errors
var (
ErrOrgNotFound = errors.New("Organization not found")
)
type Org struct {
Id int64
Version int
Name string
Created time.Time
Updated time.Time
}
// ---------------------
// COMMANDS
type CreateOrgCommand struct {
Name string `json:"name" binding:"Required"`
// initial admin user for account
UserId int64 `json:"-"`
Result Org `json:"-"`
}
type DeleteOrgCommand struct {
Id int64
}
type UpdateOrgCommand struct {
Name string `json:"name" binding:"Required"`
OrgId int64 `json:"-"`
}
type GetOrgByIdQuery struct {
Id int64
Result *Org
}
type GetOrgByNameQuery struct {
Name string
Result *Org
}
type GetOrgListQuery struct {
Result []*Org
}
type OrgDTO struct {
Id int64 `json:"id"`
Name string `json:"name"`
}
type UserOrgDTO struct {
OrgId int64 `json:"orgId"`
Name string `json:"name"`
Role RoleType `json:"role"`
IsUsing bool `json:"isUsing"`
}
+67
View File
@@ -0,0 +1,67 @@
package models
import (
"errors"
"time"
)
// Typed errors
var (
ErrInvalidRoleType = errors.New("Invalid role type")
ErrLastOrgAdmin = errors.New("Cannot remove last organization admin")
)
type RoleType string
const (
ROLE_VIEWER RoleType = "Viewer"
ROLE_EDITOR RoleType = "Editor"
ROLE_ADMIN RoleType = "Admin"
)
func (r RoleType) IsValid() bool {
return r == ROLE_VIEWER || r == ROLE_ADMIN || r == ROLE_EDITOR
}
type OrgUser struct {
OrgId int64
UserId int64
Role RoleType
Created time.Time
Updated time.Time
}
// ---------------------
// COMMANDS
type RemoveOrgUserCommand struct {
UserId int64
OrgId int64
}
type AddOrgUserCommand struct {
LoginOrEmail string `json:"loginOrEmail" binding:"Required"`
Role RoleType `json:"role" binding:"Required"`
OrgId int64 `json:"-"`
UserId int64 `json:"-"`
}
// ----------------------
// QUERIES
type GetOrgUsersQuery struct {
OrgId int64
Result []*OrgUserDTO
}
// ----------------------
// Projections and DTOs
type OrgUserDTO struct {
OrgId int64 `json:"orgId"`
UserId int64 `json:"userId"`
Email string `json:"email"`
Login string `json:"login"`
Role string `json:"role"`
}
+3 -4
View File
@@ -11,7 +11,6 @@ type DashboardSearchHit struct {
Title string `json:"title"`
Slug string `json:"slug"`
Tags []string `json:"tags"`
Url string `json:"url"`
IsStarred bool `json:"isStarred"`
}
@@ -23,7 +22,7 @@ type DashboardTagCloudItem struct {
type SearchDashboardsQuery struct {
Title string
Tag string
AccountId int64
OrgId int64
UserId int64
Limit int
IsStarred bool
@@ -32,6 +31,6 @@ type SearchDashboardsQuery struct {
}
type GetDashboardTagsQuery struct {
AccountId int64
Result []*DashboardTagCloudItem
OrgId int64
Result []*DashboardTagCloudItem
}
+49 -20
View File
@@ -11,18 +11,20 @@ var (
)
type User struct {
Id int64
Version int
Email string
Name string
Login string
Password string
Salt string
Rands string
Company string
Id int64
Version int
Email string
Name string
Login string
Password string
Salt string
Rands string
Company string
EmailVerified bool
Theme string
IsAdmin bool
AccountId int64
IsAdmin bool
OrgId int64
Created time.Time
Updated time.Time
@@ -50,9 +52,25 @@ type UpdateUserCommand struct {
UserId int64 `json:"-"`
}
type SetUsingAccountCommand struct {
UserId int64
AccountId int64
type ChangeUserPasswordCommand struct {
OldPassword string `json:"oldPassword"`
NewPassword string `json:"newPassword"`
UserId int64 `json:"-"`
}
type UpdateUserPermissionsCommand struct {
IsGrafanaAdmin bool
UserId int64 `json:"-"`
}
type DeleteUserCommand struct {
UserId int64
}
type SetUsingOrgCommand struct {
UserId int64
OrgId int64
}
// ----------------------
@@ -63,6 +81,11 @@ type GetUserByLoginQuery struct {
Result *User
}
type GetUserByIdQuery struct {
Id int64
Result *User
}
type GetSignedInUserQuery struct {
UserId int64
Result *SignedInUser
@@ -81,14 +104,19 @@ type SearchUsersQuery struct {
Result []*UserSearchHitDTO
}
type GetUserOrgListQuery struct {
UserId int64
Result []*UserOrgDTO
}
// ------------------------
// DTO & Projections
type SignedInUser struct {
UserId int64
AccountId int64
AccountName string
AccountRole RoleType
OrgId int64
OrgName string
OrgRole RoleType
Login string
Name string
Email string
@@ -97,9 +125,10 @@ type SignedInUser struct {
}
type UserDTO struct {
Email string `json:"email"`
Name string `json:"name"`
Login string `json:"login"`
Email string `json:"email"`
Name string `json:"name"`
Login string `json:"login"`
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
}
type UserSearchHitDTO struct {
-103
View File
@@ -1,103 +0,0 @@
package sqlstore
import (
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/events"
m "github.com/grafana/grafana/pkg/models"
)
func init() {
bus.AddHandler("sql", GetAccountById)
bus.AddHandler("sql", CreateAccount)
bus.AddHandler("sql", SetUsingAccount)
bus.AddHandler("sql", UpdateAccount)
bus.AddHandler("sql", GetAccountByName)
}
func GetAccountById(query *m.GetAccountByIdQuery) error {
var account m.Account
exists, err := x.Id(query.Id).Get(&account)
if err != nil {
return err
}
if !exists {
return m.ErrAccountNotFound
}
query.Result = &account
return nil
}
func GetAccountByName(query *m.GetAccountByNameQuery) error {
var account m.Account
exists, err := x.Where("name=?", query.Name).Get(&account)
if err != nil {
return err
}
if !exists {
return m.ErrAccountNotFound
}
query.Result = &account
return nil
}
func CreateAccount(cmd *m.CreateAccountCommand) error {
return inTransaction2(func(sess *session) error {
account := m.Account{
Name: cmd.Name,
Created: time.Now(),
Updated: time.Now(),
}
if _, err := sess.Insert(&account); err != nil {
return err
}
user := m.AccountUser{
AccountId: account.Id,
UserId: cmd.UserId,
Role: m.ROLE_ADMIN,
Created: time.Now(),
Updated: time.Now(),
}
_, err := sess.Insert(&user)
cmd.Result = account
sess.publishAfterCommit(&events.AccountCreated{
Timestamp: account.Created,
Id: account.Id,
Name: account.Name,
})
return err
})
}
func UpdateAccount(cmd *m.UpdateAccountCommand) error {
return inTransaction2(func(sess *session) error {
account := m.Account{
Name: cmd.Name,
Updated: time.Now(),
}
if _, err := sess.Id(cmd.AccountId).Update(&account); err != nil {
return err
}
sess.publishAfterCommit(&events.AccountUpdated{
Timestamp: account.Updated,
Id: account.Id,
Name: account.Name,
})
return nil
})
}
-67
View File
@@ -1,67 +0,0 @@
package sqlstore
import (
"fmt"
"time"
"github.com/go-xorm/xorm"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
)
func init() {
bus.AddHandler("sql", AddAccountUser)
bus.AddHandler("sql", RemoveAccountUser)
bus.AddHandler("sql", GetAccountUsers)
}
func AddAccountUser(cmd *m.AddAccountUserCommand) error {
return inTransaction(func(sess *xorm.Session) error {
entity := m.AccountUser{
AccountId: cmd.AccountId,
UserId: cmd.UserId,
Role: cmd.Role,
Created: time.Now(),
Updated: time.Now(),
}
_, err := sess.Insert(&entity)
return err
})
}
func GetAccountUsers(query *m.GetAccountUsersQuery) error {
query.Result = make([]*m.AccountUserDTO, 0)
sess := x.Table("account_user")
sess.Join("INNER", "user", fmt.Sprintf("account_user.user_id=%s.id", x.Dialect().Quote("user")))
sess.Where("account_user.account_id=?", query.AccountId)
sess.Cols("account_user.account_id", "account_user.user_id", "user.email", "user.login", "account_user.role")
sess.Asc("user.email", "user.login")
err := sess.Find(&query.Result)
return err
}
func RemoveAccountUser(cmd *m.RemoveAccountUserCommand) error {
return inTransaction(func(sess *xorm.Session) error {
var rawSql = "DELETE FROM account_user WHERE account_id=? and user_id=?"
_, err := sess.Exec(rawSql, cmd.AccountId, cmd.UserId)
if err != nil {
return err
}
// validate that there is an admin user left
res, err := sess.Query("SELECT 1 from account_user WHERE account_id=? and role='Admin'", cmd.AccountId)
if err != nil {
return err
}
if len(res) == 0 {
return m.ErrLastAccountAdmin
}
return err
})
}
+16 -16
View File
@@ -17,7 +17,7 @@ func init() {
}
func GetApiKeys(query *m.GetApiKeysQuery) error {
sess := x.Limit(100, 0).Where("account_id=?", query.AccountId).Asc("name")
sess := x.Limit(100, 0).Where("org_id=?", query.OrgId).Asc("name")
query.Result = make([]*m.ApiKey, 0)
return sess.Find(&query.Result)
@@ -25,8 +25,8 @@ func GetApiKeys(query *m.GetApiKeysQuery) error {
func DeleteApiKey(cmd *m.DeleteApiKeyCommand) error {
return inTransaction(func(sess *xorm.Session) error {
var rawSql = "DELETE FROM api_key WHERE id=? and account_id=?"
_, err := sess.Exec(rawSql, cmd.Id, cmd.AccountId)
var rawSql = "DELETE FROM api_key WHERE id=? and org_id=?"
_, err := sess.Exec(rawSql, cmd.Id, cmd.OrgId)
return err
})
}
@@ -34,12 +34,12 @@ func DeleteApiKey(cmd *m.DeleteApiKeyCommand) error {
func AddApiKey(cmd *m.AddApiKeyCommand) error {
return inTransaction(func(sess *xorm.Session) error {
t := m.ApiKey{
AccountId: cmd.AccountId,
Name: cmd.Name,
Role: cmd.Role,
Key: cmd.Key,
Created: time.Now(),
Updated: time.Now(),
OrgId: cmd.OrgId,
Name: cmd.Name,
Role: cmd.Role,
Key: cmd.Key,
Created: time.Now(),
Updated: time.Now(),
}
if _, err := sess.Insert(&t); err != nil {
@@ -53,20 +53,20 @@ func AddApiKey(cmd *m.AddApiKeyCommand) error {
func UpdateApiKey(cmd *m.UpdateApiKeyCommand) error {
return inTransaction(func(sess *xorm.Session) error {
t := m.ApiKey{
Id: cmd.Id,
AccountId: cmd.AccountId,
Name: cmd.Name,
Role: cmd.Role,
Updated: time.Now(),
Id: cmd.Id,
OrgId: cmd.OrgId,
Name: cmd.Name,
Role: cmd.Role,
Updated: time.Now(),
}
_, err := sess.Where("id=? and account_id=?", t.Id, t.AccountId).Update(&t)
_, err := sess.Where("id=? and org_id=?", t.Id, t.OrgId).Update(&t)
return err
})
}
func GetApiKeyByKey(query *m.GetApiKeyByKeyQuery) error {
var apikey m.ApiKey
has, err := x.Where("key=?", query.Key).Get(&apikey)
has, err := x.Where("`key`=?", query.Key).Get(&apikey)
if err != nil {
return err
+31
View File
@@ -0,0 +1,31 @@
package sqlstore
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
m "github.com/grafana/grafana/pkg/models"
)
func TestApiKeyDataAccess(t *testing.T) {
Convey("Testing API Key data access", t, func() {
InitTestDB(t)
Convey("Given saved api key", func() {
cmd := m.AddApiKeyCommand{OrgId: 1, Key: "hello"}
err := AddApiKey(&cmd)
So(err, ShouldBeNil)
Convey("Should be able to get key by key", func() {
query := m.GetApiKeyByKeyQuery{Key: "hello"}
err = GetApiKeyByKey(&query)
So(err, ShouldBeNil)
So(query.Result, ShouldNotBeNil)
})
})
})
}
+8 -8
View File
@@ -22,7 +22,7 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
dash := cmd.GetDashboardModel()
// try get existing dashboard
existing := m.Dashboard{Slug: dash.Slug, AccountId: dash.AccountId}
existing := m.Dashboard{Slug: dash.Slug, OrgId: dash.OrgId}
hasExisting, err := sess.Get(&existing)
if err != nil {
return err
@@ -61,7 +61,7 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
}
func GetDashboard(query *m.GetDashboardQuery) error {
dashboard := m.Dashboard{Slug: query.Slug, AccountId: query.AccountId}
dashboard := m.Dashboard{Slug: query.Slug, OrgId: query.OrgId}
has, err := x.Get(&dashboard)
if err != nil {
return err
@@ -98,9 +98,9 @@ func SearchDashboards(query *m.SearchDashboardsQuery) error {
sql.WriteString(" INNER JOIN star on star.dashboard_id = dashboard.id")
}
sql.WriteString(` WHERE dashboard.account_id=?`)
sql.WriteString(` WHERE dashboard.org_id=?`)
params = append(params, query.AccountId)
params = append(params, query.OrgId)
if query.IsStarred {
sql.WriteString(` AND star.user_id=?`)
@@ -158,11 +158,11 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
term
FROM dashboard
INNER JOIN dashboard_tag on dashboard_tag.dashboard_id = dashboard.id
WHERE dashboard.account_id=?
WHERE dashboard.org_id=?
GROUP BY term`
query.Result = make([]*m.DashboardTagCloudItem, 0)
sess := x.Sql(sql, query.AccountId)
sess := x.Sql(sql, query.OrgId)
err := sess.Find(&query.Result)
return err
}
@@ -171,8 +171,8 @@ func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
sess := x.NewSession()
defer sess.Close()
rawSql := "DELETE FROM Dashboard WHERE account_id=? and slug=?"
_, err := sess.Exec(rawSql, cmd.AccountId, cmd.Slug)
rawSql := "DELETE FROM dashboard WHERE org_id=? and slug=?"
_, err := sess.Exec(rawSql, cmd.OrgId, cmd.Slug)
return err
}
+11 -11
View File
@@ -8,9 +8,9 @@ import (
m "github.com/grafana/grafana/pkg/models"
)
func insertTestDashboard(title string, accountId int64, tags ...interface{}) *m.Dashboard {
func insertTestDashboard(title string, orgId int64, tags ...interface{}) *m.Dashboard {
cmd := m.SaveDashboardCommand{
AccountId: accountId,
OrgId: orgId,
Dashboard: map[string]interface{}{
"id": nil,
"title": title,
@@ -40,8 +40,8 @@ func TestDashboardDataAccess(t *testing.T) {
Convey("Should be able to get dashboard", func() {
query := m.GetDashboardQuery{
Slug: "test-dash-23",
AccountId: 1,
Slug: "test-dash-23",
OrgId: 1,
}
err := GetDashboard(&query)
@@ -53,8 +53,8 @@ func TestDashboardDataAccess(t *testing.T) {
Convey("Should be able to search for dashboard", func() {
query := m.SearchDashboardsQuery{
Title: "test",
AccountId: 1,
Title: "test",
OrgId: 1,
}
err := SearchDashboards(&query)
@@ -66,8 +66,8 @@ func TestDashboardDataAccess(t *testing.T) {
})
Convey("Should be able to search for dashboards using tags", func() {
query1 := m.SearchDashboardsQuery{Tag: "webapp", AccountId: 1}
query2 := m.SearchDashboardsQuery{Tag: "tagdoesnotexist", AccountId: 1}
query1 := m.SearchDashboardsQuery{Tag: "webapp", OrgId: 1}
query2 := m.SearchDashboardsQuery{Tag: "tagdoesnotexist", OrgId: 1}
err := SearchDashboards(&query1)
err = SearchDashboards(&query2)
@@ -79,7 +79,7 @@ func TestDashboardDataAccess(t *testing.T) {
Convey("Should not be able to save dashboard with same name", func() {
cmd := m.SaveDashboardCommand{
AccountId: 1,
OrgId: 1,
Dashboard: map[string]interface{}{
"id": nil,
"title": "test dash 23",
@@ -92,7 +92,7 @@ func TestDashboardDataAccess(t *testing.T) {
})
Convey("Should be able to get dashboard tags", func() {
query := m.GetDashboardTagsQuery{AccountId: 1}
query := m.GetDashboardTagsQuery{OrgId: 1}
err := GetDashboardTags(&query)
So(err, ShouldBeNil)
@@ -113,7 +113,7 @@ func TestDashboardDataAccess(t *testing.T) {
})
Convey("Should be able to search for starred dashboards", func() {
query := m.SearchDashboardsQuery{AccountId: 1, UserId: 10, IsStarred: true}
query := m.SearchDashboardsQuery{OrgId: 1, UserId: 10, IsStarred: true}
err := SearchDashboards(&query)
So(err, ShouldBeNil)
+20 -9
View File
@@ -15,10 +15,21 @@ func init() {
bus.AddHandler("sql", DeleteDataSource)
bus.AddHandler("sql", UpdateDataSource)
bus.AddHandler("sql", GetDataSourceById)
bus.AddHandler("sql", GetDataSourceByName)
}
func GetDataSourceById(query *m.GetDataSourceByIdQuery) error {
sess := x.Limit(100, 0).Where("account_id=? AND id=?", query.AccountId, query.Id)
sess := x.Limit(100, 0).Where("org_id=? AND id=?", query.OrgId, query.Id)
has, err := sess.Get(&query.Result)
if !has {
return m.ErrDataSourceNotFound
}
return err
}
func GetDataSourceByName(query *m.GetDataSourceByNameQuery) error {
sess := x.Limit(100, 0).Where("org_id=? AND name=?", query.OrgId, query.Name)
has, err := sess.Get(&query.Result)
if !has {
@@ -28,7 +39,7 @@ func GetDataSourceById(query *m.GetDataSourceByIdQuery) error {
}
func GetDataSources(query *m.GetDataSourcesQuery) error {
sess := x.Limit(100, 0).Where("account_id=?", query.AccountId).Asc("name")
sess := x.Limit(100, 0).Where("org_id=?", query.OrgId).Asc("name")
query.Result = make([]*m.DataSource, 0)
return sess.Find(&query.Result)
@@ -36,8 +47,8 @@ func GetDataSources(query *m.GetDataSourcesQuery) error {
func DeleteDataSource(cmd *m.DeleteDataSourceCommand) error {
return inTransaction(func(sess *xorm.Session) error {
var rawSql = "DELETE FROM data_source WHERE id=? and account_id=?"
_, err := sess.Exec(rawSql, cmd.Id, cmd.AccountId)
var rawSql = "DELETE FROM data_source WHERE id=? and org_id=?"
_, err := sess.Exec(rawSql, cmd.Id, cmd.OrgId)
return err
})
}
@@ -46,7 +57,7 @@ func AddDataSource(cmd *m.AddDataSourceCommand) error {
return inTransaction(func(sess *xorm.Session) error {
ds := &m.DataSource{
AccountId: cmd.AccountId,
OrgId: cmd.OrgId,
Name: cmd.Name,
Type: cmd.Type,
Access: cmd.Access,
@@ -74,8 +85,8 @@ func AddDataSource(cmd *m.AddDataSourceCommand) error {
func updateIsDefaultFlag(ds *m.DataSource, sess *xorm.Session) error {
// Handle is default flag
if ds.IsDefault {
rawSql := "UPDATE data_source SET is_default = 0 WHERE account_id=? AND id <> ?"
if _, err := sess.Exec(rawSql, ds.AccountId, ds.Id); err != nil {
rawSql := "UPDATE data_source SET is_default = 0 WHERE org_id=? AND id <> ?"
if _, err := sess.Exec(rawSql, ds.OrgId, ds.Id); err != nil {
return err
}
}
@@ -87,7 +98,7 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
return inTransaction(func(sess *xorm.Session) error {
ds := &m.DataSource{
Id: cmd.Id,
AccountId: cmd.AccountId,
OrgId: cmd.OrgId,
Name: cmd.Name,
Type: cmd.Type,
Access: cmd.Access,
@@ -101,7 +112,7 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
sess.UseBool("is_default")
_, err := sess.Where("id=? and account_id=?", ds.Id, ds.AccountId).Update(ds)
_, err := sess.Where("id=? and org_id=?", ds.Id, ds.OrgId).Update(ds)
if err != nil {
return err
}
+15 -15
View File
@@ -42,16 +42,16 @@ func TestDataAccess(t *testing.T) {
Convey("Can add datasource", func() {
err := AddDataSource(&m.AddDataSourceCommand{
AccountId: 10,
Type: m.DS_INFLUXDB,
Access: m.DS_ACCESS_DIRECT,
Url: "http://test",
Database: "site",
OrgId: 10,
Type: m.DS_INFLUXDB,
Access: m.DS_ACCESS_DIRECT,
Url: "http://test",
Database: "site",
})
So(err, ShouldBeNil)
query := m.GetDataSourcesQuery{AccountId: 10}
query := m.GetDataSourcesQuery{OrgId: 10}
err = GetDataSources(&query)
So(err, ShouldBeNil)
@@ -59,33 +59,33 @@ func TestDataAccess(t *testing.T) {
ds := query.Result[0]
So(ds.AccountId, ShouldEqual, 10)
So(ds.OrgId, ShouldEqual, 10)
So(ds.Database, ShouldEqual, "site")
})
Convey("Given a datasource", func() {
AddDataSource(&m.AddDataSourceCommand{
AccountId: 10,
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_DIRECT,
Url: "http://test",
OrgId: 10,
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_DIRECT,
Url: "http://test",
})
query := m.GetDataSourcesQuery{AccountId: 10}
query := m.GetDataSourcesQuery{OrgId: 10}
GetDataSources(&query)
ds := query.Result[0]
Convey("Can delete datasource", func() {
err := DeleteDataSource(&m.DeleteDataSourceCommand{Id: ds.Id, AccountId: ds.AccountId})
err := DeleteDataSource(&m.DeleteDataSourceCommand{Id: ds.Id, OrgId: ds.OrgId})
So(err, ShouldBeNil)
GetDataSources(&query)
So(len(query.Result), ShouldEqual, 0)
})
Convey("Can not delete datasource with wrong accountId", func() {
err := DeleteDataSource(&m.DeleteDataSourceCommand{Id: ds.Id, AccountId: 123123})
Convey("Can not delete datasource with wrong orgId", func() {
err := DeleteDataSource(&m.DeleteDataSourceCommand{Id: ds.Id, OrgId: 123123})
So(err, ShouldBeNil)
GetDataSources(&query)
-175
View File
@@ -1,175 +0,0 @@
package sqlstore
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
// --- Migration Guide line ---
// 1. Never change a migration that is committed and pushed to master
// 2. Always add new migrations (to change or undo previous migrations)
// 3. Some migraitons are not yet written (rename column, table, drop table, index etc)
func addMigrations(mg *Migrator) {
addMigrationLogMigrations(mg)
addUserMigrations(mg)
addStarMigrations(mg)
addAccountMigrations(mg)
addDashboardMigration(mg)
addDataSourceMigration(mg)
addApiKeyMigrations(mg)
}
func addMigrationLogMigrations(mg *Migrator) {
mg.AddMigration("create migration_log table", new(AddTableMigration).
Name("migration_log").WithColumns(
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "migration_id", Type: DB_NVarchar, Length: 255},
&Column{Name: "sql", Type: DB_Text},
&Column{Name: "success", Type: DB_Bool},
&Column{Name: "error", Type: DB_Text},
&Column{Name: "timestamp", Type: DB_DateTime},
))
}
func addUserMigrations(mg *Migrator) {
mg.AddMigration("create user table", new(AddTableMigration).
Name("user").WithColumns(
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "version", Type: DB_Int, Nullable: false},
&Column{Name: "login", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "email", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "salt", Type: DB_NVarchar, Length: 50, Nullable: true},
&Column{Name: "rands", Type: DB_NVarchar, Length: 50, Nullable: true},
&Column{Name: "company", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "is_admin", Type: DB_Bool, Nullable: false},
&Column{Name: "created", Type: DB_DateTime, Nullable: false},
&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
))
//------- user table indexes ------------------
mg.AddMigration("add unique index user.login", new(AddIndexMigration).
Table("user").Columns("login").Unique())
mg.AddMigration("add unique index user.email", new(AddIndexMigration).
Table("user").Columns("email").Unique())
}
func addStarMigrations(mg *Migrator) {
mg.AddMigration("create star table", new(AddTableMigration).
Name("star").WithColumns(
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "user_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "dashboard_id", Type: DB_BigInt, Nullable: false},
))
mg.AddMigration("add unique index star.user_id_dashboard_id", new(AddIndexMigration).
Table("star").Columns("user_id", "dashboard_id").Unique())
}
func addAccountMigrations(mg *Migrator) {
mg.AddMigration("create account table", new(AddTableMigration).
Name("account").WithColumns(
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "version", Type: DB_Int, Nullable: false},
&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "created", Type: DB_DateTime, Nullable: false},
&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
))
mg.AddMigration("add unique index account.name", new(AddIndexMigration).
Table("account").Columns("name").Unique())
//------- account_user table -------------------
mg.AddMigration("create account_user table", new(AddTableMigration).
Name("account_user").WithColumns(
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "account_id", Type: DB_BigInt},
&Column{Name: "user_id", Type: DB_BigInt},
&Column{Name: "role", Type: DB_NVarchar, Length: 20},
&Column{Name: "created", Type: DB_DateTime},
&Column{Name: "updated", Type: DB_DateTime},
))
mg.AddMigration("add unique index account_user_aid_uid", new(AddIndexMigration).
Name("aid_uid").Table("account_user").Columns("account_id", "user_id").Unique())
}
func addDashboardMigration(mg *Migrator) {
mg.AddMigration("create dashboard table", new(AddTableMigration).
Name("dashboard").WithColumns(
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "version", Type: DB_Int, Nullable: false},
&Column{Name: "slug", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "data", Type: DB_Text, Nullable: false},
&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "created", Type: DB_DateTime, Nullable: false},
&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
))
mg.AddMigration("create dashboard_tag table", new(AddTableMigration).
Name("dashboard_tag").WithColumns(
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "dashboard_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "term", Type: DB_NVarchar, Length: 50, Nullable: false},
))
//------- indexes ------------------
mg.AddMigration("add index dashboard.account_id", new(AddIndexMigration).
Table("dashboard").Columns("account_id"))
mg.AddMigration("add unique index dashboard_account_id_slug", new(AddIndexMigration).
Table("dashboard").Columns("account_id", "slug").Unique())
mg.AddMigration("add unique index dashboard_tag.dasboard_id_term", new(AddIndexMigration).
Table("dashboard_tag").Columns("dashboard_id", "term").Unique())
}
func addDataSourceMigration(mg *Migrator) {
mg.AddMigration("create data_source table", new(AddTableMigration).
Name("data_source").WithColumns(
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "version", Type: DB_Int, Nullable: false},
&Column{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "access", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "url", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "user", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "database", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "basic_auth", Type: DB_Bool, Nullable: false},
&Column{Name: "basic_auth_user", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "basic_auth_password", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "is_default", Type: DB_Bool, Nullable: false},
&Column{Name: "created", Type: DB_DateTime, Nullable: false},
&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
))
//------- indexes ------------------
mg.AddMigration("add index data_source.account_id", new(AddIndexMigration).
Table("data_source").Columns("account_id"))
mg.AddMigration("add unique index data_source.account_id_name", new(AddIndexMigration).
Table("data_source").Columns("account_id", "name").Unique())
}
func addApiKeyMigrations(mg *Migrator) {
mg.AddMigration("create api_key table", new(AddTableMigration).
Name("api_key").WithColumns(
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "key", Type: DB_Varchar, Length: 64, Nullable: false},
&Column{Name: "role", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "created", Type: DB_DateTime, Nullable: false},
&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
))
//------- indexes ------------------
mg.AddMigration("add index api_key.account_id", new(AddIndexMigration).
Table("api_key").Columns("account_id"))
mg.AddMigration("add index api_key.account_id_name", new(AddIndexMigration).
Table("api_key").Columns("account_id", "name").Unique())
}
@@ -0,0 +1,75 @@
package migrations
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
func addApiKeyMigrations(mg *Migrator) {
apiKeyV1 := Table{
Name: "api_key",
Columns: []*Column{
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "key", Type: DB_Varchar, Length: 64, Nullable: false},
&Column{Name: "role", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "created", Type: DB_DateTime, Nullable: false},
&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
},
Indices: []*Index{
&Index{Cols: []string{"account_id"}},
&Index{Cols: []string{"key"}, Type: UniqueIndex},
&Index{Cols: []string{"account_id", "name"}, Type: UniqueIndex},
},
}
// create table
mg.AddMigration("create api_key table", NewAddTableMigration(apiKeyV1))
// create indices
mg.AddMigration("add index api_key.account_id", NewAddIndexMigration(apiKeyV1, apiKeyV1.Indices[0]))
mg.AddMigration("add index api_key.key", NewAddIndexMigration(apiKeyV1, apiKeyV1.Indices[1]))
mg.AddMigration("add index api_key.account_id_name", NewAddIndexMigration(apiKeyV1, apiKeyV1.Indices[2]))
// ---------------------
// account -> org changes
// drop indexes
addDropAllIndicesMigrations(mg, "v1", apiKeyV1)
// rename table
addTableRenameMigration(mg, "api_key", "api_key_v1", "v1")
apiKeyV2 := Table{
Name: "api_key",
Columns: []*Column{
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "org_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "key", Type: DB_Varchar, Length: 64, Nullable: false},
&Column{Name: "role", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "created", Type: DB_DateTime, Nullable: false},
&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
},
Indices: []*Index{
&Index{Cols: []string{"org_id"}},
&Index{Cols: []string{"key"}, Type: UniqueIndex},
&Index{Cols: []string{"org_id", "name"}, Type: UniqueIndex},
},
}
// create v2 table
mg.AddMigration("create api_key table v2", NewAddTableMigration(apiKeyV2))
// add v2 indíces
addTableIndicesMigrations(mg, "v2", apiKeyV2)
//------- copy data from v1 to v2 -------------------
mg.AddMigration("copy api_key v1 to v2", NewCopyTableDataMigration("api_key", "api_key_v1", map[string]string{
"id": "id",
"org_id": "account_id",
"name": "name",
"key": "key",
"role": "role",
"created": "created",
"updated": "updated",
}))
mg.AddMigration("Drop old table api_key_v1", NewDropTableMigration("api_key_v1"))
}
@@ -0,0 +1,26 @@
package migrations
import (
"fmt"
. "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
)
func addDropAllIndicesMigrations(mg *Migrator, versionSuffix string, table Table) {
for _, index := range table.Indices {
migrationId := fmt.Sprintf("drop index %s - %s", index.XName(table.Name), versionSuffix)
mg.AddMigration(migrationId, NewDropIndexMigration(table, index))
}
}
func addTableIndicesMigrations(mg *Migrator, versionSuffix string, table Table) {
for _, index := range table.Indices {
migrationId := fmt.Sprintf("create index %s - %s", index.XName(table.Name), versionSuffix)
mg.AddMigration(migrationId, NewAddIndexMigration(table, index))
}
}
func addTableRenameMigration(mg *Migrator, oldName string, newName string, versionSuffix string) {
migrationId := fmt.Sprintf("Rename table %s to %s - %s", oldName, newName, versionSuffix)
mg.AddMigration(migrationId, NewRenameTableMigration(oldName, newName))
}
@@ -0,0 +1,89 @@
package migrations
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
func addDashboardMigration(mg *Migrator) {
var dashboardV1 = Table{
Name: "dashboard",
Columns: []*Column{
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "version", Type: DB_Int, Nullable: false},
&Column{Name: "slug", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "data", Type: DB_Text, Nullable: false},
&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "created", Type: DB_DateTime, Nullable: false},
&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
},
Indices: []*Index{
&Index{Cols: []string{"account_id"}},
&Index{Cols: []string{"account_id", "slug"}, Type: UniqueIndex},
},
}
mg.AddMigration("create dashboard table", NewAddTableMigration(dashboardV1))
//------- indexes ------------------
mg.AddMigration("add index dashboard.account_id", NewAddIndexMigration(dashboardV1, dashboardV1.Indices[0]))
mg.AddMigration("add unique index dashboard_account_id_slug", NewAddIndexMigration(dashboardV1, dashboardV1.Indices[1]))
dashboardTagV1 := Table{
Name: "dashboard_tag",
Columns: []*Column{
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "dashboard_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "term", Type: DB_NVarchar, Length: 50, Nullable: false},
},
Indices: []*Index{
&Index{Cols: []string{"dashboard_id", "term"}, Type: UniqueIndex},
},
}
mg.AddMigration("create dashboard_tag table", NewAddTableMigration(dashboardTagV1))
mg.AddMigration("add unique index dashboard_tag.dasboard_id_term", NewAddIndexMigration(dashboardTagV1, dashboardTagV1.Indices[0]))
// ---------------------
// account -> org changes
//------- drop dashboard indexes ------------------
addDropAllIndicesMigrations(mg, "v1", dashboardTagV1)
//------- rename table ------------------
addTableRenameMigration(mg, "dashboard", "dashboard_v1", "v1")
// dashboard v2
var dashboardV2 = Table{
Name: "dashboard",
Columns: []*Column{
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "version", Type: DB_Int, Nullable: false},
&Column{Name: "slug", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "data", Type: DB_Text, Nullable: false},
&Column{Name: "org_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "created", Type: DB_DateTime, Nullable: false},
&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
},
Indices: []*Index{
&Index{Cols: []string{"org_id"}},
&Index{Cols: []string{"org_id", "slug"}, Type: UniqueIndex},
},
}
// recreate table
mg.AddMigration("create dashboard v2", NewAddTableMigration(dashboardV2))
// recreate indices
addTableIndicesMigrations(mg, "v2", dashboardV2)
// copy data
mg.AddMigration("copy dashboard v1 to v2", NewCopyTableDataMigration("dashboard", "dashboard_v1", map[string]string{
"id": "id",
"version": "version",
"slug": "slug",
"title": "title",
"data": "data",
"org_id": "account_id",
"created": "created",
"updated": "updated",
}))
mg.AddMigration("drop table dashboard_v1", NewDropTableMigration("dashboard_v1"))
}
@@ -0,0 +1,98 @@
package migrations
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
func addDataSourceMigration(mg *Migrator) {
var tableV1 = Table{
Name: "data_source",
Columns: []*Column{
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "version", Type: DB_Int, Nullable: false},
&Column{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "access", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "url", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "user", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "database", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "basic_auth", Type: DB_Bool, Nullable: false},
&Column{Name: "basic_auth_user", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "basic_auth_password", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "is_default", Type: DB_Bool, Nullable: false},
&Column{Name: "created", Type: DB_DateTime, Nullable: false},
&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
},
Indices: []*Index{
&Index{Cols: []string{"account_id"}},
&Index{Cols: []string{"account_id", "name"}, Type: UniqueIndex},
},
}
mg.AddMigration("create data_source table", NewAddTableMigration(tableV1))
mg.AddMigration("add index data_source.account_id", NewAddIndexMigration(tableV1, tableV1.Indices[0]))
mg.AddMigration("add unique index data_source.account_id_name", NewAddIndexMigration(tableV1, tableV1.Indices[1]))
// ---------------------
// account -> org changes
// drop v1 indices
addDropAllIndicesMigrations(mg, "v1", tableV1)
// rename table
addTableRenameMigration(mg, "data_source", "data_source_v1", "v1")
// new table
var tableV2 = Table{
Name: "data_source",
Columns: []*Column{
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "org_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "version", Type: DB_Int, Nullable: false},
&Column{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "access", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "url", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "user", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "database", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "basic_auth", Type: DB_Bool, Nullable: false},
&Column{Name: "basic_auth_user", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "basic_auth_password", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "is_default", Type: DB_Bool, Nullable: false},
&Column{Name: "json_data", Type: DB_Text, Nullable: true},
&Column{Name: "created", Type: DB_DateTime, Nullable: false},
&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
},
Indices: []*Index{
&Index{Cols: []string{"org_id"}},
&Index{Cols: []string{"org_id", "name"}, Type: UniqueIndex},
},
}
// create v2 table
mg.AddMigration("create data_source table v2", NewAddTableMigration(tableV2))
// add v2 indíces
addTableIndicesMigrations(mg, "v2", tableV2)
//------- copy data from v1 to v2 -------------------
mg.AddMigration("copy data_source v1 to v2", NewCopyTableDataMigration("data_source", "data_source_v1", map[string]string{
"id": "id",
"org_id": "account_id",
"version": "version",
"type": "type",
"name": "name",
"access": "access",
"url": "password",
"user": "user",
"database": "database",
"basic_auth": "basic_auth",
"basic_auth_user": "basic_auth_user",
"basic_auth_password": "basic_auth_password",
"is_default": "is_default",
"created": "created",
"updated": "updated",
}))
mg.AddMigration("Drop old table data_source_v1", NewDropTableMigration("data_source_old"))
}
@@ -0,0 +1,51 @@
package migrations
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
// --- Migration Guide line ---
// 1. Never change a migration that is committed and pushed to master
// 2. Always add new migrations (to change or undo previous migrations)
// 3. Some migraitons are not yet written (rename column, table, drop table, index etc)
func AddMigrations(mg *Migrator) {
addMigrationLogMigrations(mg)
addUserMigrations(mg)
addStarMigrations(mg)
addOrgMigrations(mg)
addDashboardMigration(mg)
addDataSourceMigration(mg)
addApiKeyMigrations(mg)
}
func addMigrationLogMigrations(mg *Migrator) {
migrationLogV1 := Table{
Name: "migration_log",
Columns: []*Column{
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "migration_id", Type: DB_NVarchar, Length: 255},
&Column{Name: "sql", Type: DB_Text},
&Column{Name: "success", Type: DB_Bool},
&Column{Name: "error", Type: DB_Text},
&Column{Name: "timestamp", Type: DB_DateTime},
},
}
mg.AddMigration("create migration_log table", NewAddTableMigration(migrationLogV1))
}
func addStarMigrations(mg *Migrator) {
starV1 := Table{
Name: "star",
Columns: []*Column{
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "user_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "dashboard_id", Type: DB_BigInt, Nullable: false},
},
Indices: []*Index{
&Index{Cols: []string{"user_id", "dashboard_id"}, Type: UniqueIndex},
},
}
mg.AddMigration("create star table", NewAddTableMigration(starV1))
mg.AddMigration("add unique index star.user_id_dashboard_id", NewAddIndexMigration(starV1, starV1.Indices[0]))
}
@@ -1,4 +1,4 @@
package sqlstore
package migrations
import (
"fmt"
@@ -32,7 +32,7 @@ func TestMigrations(t *testing.T) {
mg := NewMigrator(x)
mg.LogLevel = log.DEBUG
addMigrations(mg)
AddMigrations(mg)
err = mg.Start()
So(err, ShouldBeNil)
@@ -0,0 +1,71 @@
package migrations
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
func addOrgMigrations(mg *Migrator) {
orgV1 := Table{
Name: "org",
Columns: []*Column{
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "version", Type: DB_Int, Nullable: false},
&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "address1", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "address2", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "city", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "state", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "zip_code", Type: DB_NVarchar, Length: 50, Nullable: true},
&Column{Name: "country", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "billing_email", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "created", Type: DB_DateTime, Nullable: false},
&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
},
Indices: []*Index{
&Index{Cols: []string{"name"}, Type: UniqueIndex},
},
}
// add org v1
mg.AddMigration("create org table v1", NewAddTableMigration(orgV1))
addTableIndicesMigrations(mg, "v1", orgV1)
orgUserV1 := Table{
Name: "org_user",
Columns: []*Column{
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "org_id", Type: DB_BigInt},
&Column{Name: "user_id", Type: DB_BigInt},
&Column{Name: "role", Type: DB_NVarchar, Length: 20},
&Column{Name: "created", Type: DB_DateTime},
&Column{Name: "updated", Type: DB_DateTime},
},
Indices: []*Index{
&Index{Cols: []string{"org_id"}},
&Index{Cols: []string{"org_id", "user_id"}, Type: UniqueIndex},
},
}
//------- org_user table -------------------
mg.AddMigration("create org_user table v1", NewAddTableMigration(orgUserV1))
addTableIndicesMigrations(mg, "v1", orgUserV1)
//------- copy data from old table-------------------
mg.AddMigration("copy data account to org", NewCopyTableDataMigration("org", "account", map[string]string{
"id": "id",
"version": "version",
"name": "name",
"created": "created",
"updated": "updated",
}).IfTableExists("account"))
mg.AddMigration("copy data account_user to org_user", NewCopyTableDataMigration("org_user", "account_user", map[string]string{
"id": "id",
"org_id": "account_id",
"user_id": "user_id",
"role": "role",
"created": "created",
"updated": "updated",
}).IfTableExists("account_user"))
mg.AddMigration("Drop old table account", NewDropTableMigration("account"))
mg.AddMigration("Drop old table account_user", NewDropTableMigration("account_user"))
}
@@ -0,0 +1,91 @@
package migrations
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
func addUserMigrations(mg *Migrator) {
userV1 := Table{
Name: "user",
Columns: []*Column{
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "version", Type: DB_Int, Nullable: false},
&Column{Name: "login", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "email", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "salt", Type: DB_NVarchar, Length: 50, Nullable: true},
&Column{Name: "rands", Type: DB_NVarchar, Length: 50, Nullable: true},
&Column{Name: "company", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "is_admin", Type: DB_Bool, Nullable: false},
&Column{Name: "created", Type: DB_DateTime, Nullable: false},
&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
},
Indices: []*Index{
&Index{Cols: []string{"login"}, Type: UniqueIndex},
&Index{Cols: []string{"email"}, Type: UniqueIndex},
},
}
// create table
mg.AddMigration("create user table", NewAddTableMigration(userV1))
// add indices
mg.AddMigration("add unique index user.login", NewAddIndexMigration(userV1, userV1.Indices[0]))
mg.AddMigration("add unique index user.email", NewAddIndexMigration(userV1, userV1.Indices[1]))
// ---------------------
// account -> org changes
//------- drop indexes ------------------
addDropAllIndicesMigrations(mg, "v1", userV1)
//------- rename table ------------------
addTableRenameMigration(mg, "user", "user_v1", "v1")
//------- recreate table with new column names ------------------
userV2 := Table{
Name: "user",
Columns: []*Column{
&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
&Column{Name: "version", Type: DB_Int, Nullable: false},
&Column{Name: "login", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "email", Type: DB_NVarchar, Length: 255, Nullable: false},
&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "salt", Type: DB_NVarchar, Length: 50, Nullable: true},
&Column{Name: "rands", Type: DB_NVarchar, Length: 50, Nullable: true},
&Column{Name: "company", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "org_id", Type: DB_BigInt, Nullable: false},
&Column{Name: "is_admin", Type: DB_Bool, Nullable: false},
&Column{Name: "email_verified", Type: DB_Bool, Nullable: true},
&Column{Name: "theme", Type: DB_NVarchar, Length: 255, Nullable: true},
&Column{Name: "created", Type: DB_DateTime, Nullable: false},
&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
},
Indices: []*Index{
&Index{Cols: []string{"login"}, Type: UniqueIndex},
&Index{Cols: []string{"email"}, Type: UniqueIndex},
},
}
mg.AddMigration("create user table v2", NewAddTableMigration(userV2))
addTableIndicesMigrations(mg, "v2", userV2)
//------- copy data from v1 to v2 -------------------
mg.AddMigration("copy data_source v1 to v2", NewCopyTableDataMigration("user", "user_v1", map[string]string{
"id": "id",
"version": "version",
"login": "login",
"email": "email",
"name": "name",
"password": "password",
"salt": "salt",
"rands": "rands",
"company": "company",
"org_id": "account_id",
"is_admin": "is_admin",
"created": "created",
"updated": "updated",
}))
mg.AddMigration("Drop old table user_v1", NewDropTableMigration("user_v1"))
}
-131
View File
@@ -1,131 +0,0 @@
package migrator
import (
"fmt"
"strings"
)
type MigrationBase struct {
id string
}
func (m *MigrationBase) Id() string {
return m.id
}
func (m *MigrationBase) SetId(id string) {
m.id = id
}
type RawSqlMigration struct {
MigrationBase
sqlite string
mysql string
}
func (m *RawSqlMigration) Sql(dialect Dialect) string {
switch dialect.DriverName() {
case MYSQL:
return m.mysql
case SQLITE:
return m.sqlite
}
panic("db type not supported")
}
func (m *RawSqlMigration) Sqlite(sql string) *RawSqlMigration {
m.sqlite = sql
return m
}
func (m *RawSqlMigration) Mysql(sql string) *RawSqlMigration {
m.mysql = sql
return m
}
type AddColumnMigration struct {
MigrationBase
tableName string
column *Column
}
func (m *AddColumnMigration) Table(tableName string) *AddColumnMigration {
m.tableName = tableName
return m
}
func (m *AddColumnMigration) Column(col *Column) *AddColumnMigration {
m.column = col
return m
}
func (m *AddColumnMigration) Sql(dialect Dialect) string {
return dialect.AddColumnSql(m.tableName, m.column)
}
type AddIndexMigration struct {
MigrationBase
tableName string
index Index
}
func (m *AddIndexMigration) Name(name string) *AddIndexMigration {
m.index.Name = name
return m
}
func (m *AddIndexMigration) Table(tableName string) *AddIndexMigration {
m.tableName = tableName
return m
}
func (m *AddIndexMigration) Unique() *AddIndexMigration {
m.index.Type = UniqueIndex
return m
}
func (m *AddIndexMigration) Columns(columns ...string) *AddIndexMigration {
m.index.Cols = columns
return m
}
func (m *AddIndexMigration) Sql(dialect Dialect) string {
if m.index.Name == "" {
m.index.Name = fmt.Sprintf("%s", strings.Join(m.index.Cols, "_"))
}
return dialect.CreateIndexSql(m.tableName, &m.index)
}
type AddTableMigration struct {
MigrationBase
table Table
}
func (m *AddTableMigration) Sql(d Dialect) string {
return d.CreateTableSql(&m.table)
}
func (m *AddTableMigration) Name(name string) *AddTableMigration {
m.table.Name = name
return m
}
func (m *AddTableMigration) WithColumns(columns ...*Column) *AddTableMigration {
for _, col := range columns {
m.table.Columns = append(m.table.Columns, col)
if col.IsPrimaryKey {
m.table.PrimaryKeys = append(m.table.PrimaryKeys, col.Name)
}
}
return m
}
func (m *AddTableMigration) WithColumn(col *Column) *AddTableMigration {
m.table.Columns = append(m.table.Columns, col)
if col.IsPrimaryKey {
m.table.PrimaryKeys = append(m.table.PrimaryKeys, col.Name)
}
return m
}
@@ -0,0 +1,13 @@
package migrator
type MigrationCondition interface {
Sql(dialect Dialect) (string, []interface{})
}
type IfTableExistsCondition struct {
TableName string
}
func (c *IfTableExistsCondition) Sql(dialect Dialect) (string, []interface{}) {
return dialect.TableCheckSql(c.TableName)
}
+40 -4
View File
@@ -20,8 +20,12 @@ type Dialect interface {
CreateIndexSql(tableName string, index *Index) string
CreateTableSql(table *Table) string
AddColumnSql(tableName string, Col *Column) string
CopyTableData(sourceTable string, targetTable string, sourceCols []string, targetCols []string) string
DropTable(tableName string) string
DropIndexSql(tableName string, index *Index) string
TableCheckSql(tableName string) (string, []interface{})
RenameTable(oldName string, newName string) string
}
func NewDialect(name string) Dialect {
@@ -101,15 +105,47 @@ func (db *BaseDialect) AddColumnSql(tableName string, col *Column) string {
func (db *BaseDialect) CreateIndexSql(tableName string, index *Index) string {
quote := db.dialect.Quote
var unique string
var idxName string
if index.Type == UniqueIndex {
unique = " UNIQUE"
idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name)
} else {
idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name)
}
idxName := index.XName(tableName)
return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v);", unique,
quote(idxName), quote(tableName),
quote(strings.Join(index.Cols, quote(","))))
}
func (db *BaseDialect) QuoteColList(cols []string) string {
var sourceColsSql = ""
for _, col := range cols {
sourceColsSql += db.dialect.Quote(col)
sourceColsSql += "\n, "
}
return strings.TrimSuffix(sourceColsSql, "\n, ")
}
func (db *BaseDialect) CopyTableData(sourceTable string, targetTable string, sourceCols []string, targetCols []string) string {
sourceColsSql := db.QuoteColList(sourceCols)
targetColsSql := db.QuoteColList(targetCols)
quote := db.dialect.Quote
return fmt.Sprintf("INSERT INTO %s (%s) SELECT %s FROM %s", quote(targetTable), targetColsSql, sourceColsSql, quote(sourceTable))
}
func (db *BaseDialect) DropTable(tableName string) string {
quote := db.dialect.Quote
return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName))
}
func (db *BaseDialect) RenameTable(oldName string, newName string) string {
quote := db.dialect.Quote
return fmt.Sprintf("ALTER TABLE %s RENAME TO %s", quote(oldName), quote(newName))
}
func (db *BaseDialect) DropIndexSql(tableName string, index *Index) string {
quote := db.dialect.Quote
var name string
name = index.XName(tableName)
return fmt.Sprintf("DROP INDEX %v ON %s", quote(name), quote(tableName))
}
@@ -0,0 +1,190 @@
package migrator
import (
"fmt"
"strings"
)
type MigrationBase struct {
id string
Condition MigrationCondition
}
func (m *MigrationBase) Id() string {
return m.id
}
func (m *MigrationBase) SetId(id string) {
m.id = id
}
func (m *MigrationBase) GetCondition() MigrationCondition {
return m.Condition
}
type RawSqlMigration struct {
MigrationBase
sqlite string
mysql string
}
func (m *RawSqlMigration) Sql(dialect Dialect) string {
switch dialect.DriverName() {
case MYSQL:
return m.mysql
case SQLITE:
return m.sqlite
}
panic("db type not supported")
}
func (m *RawSqlMigration) Sqlite(sql string) *RawSqlMigration {
m.sqlite = sql
return m
}
func (m *RawSqlMigration) Mysql(sql string) *RawSqlMigration {
m.mysql = sql
return m
}
type AddColumnMigration struct {
MigrationBase
tableName string
column *Column
}
func (m *AddColumnMigration) Table(tableName string) *AddColumnMigration {
m.tableName = tableName
return m
}
func (m *AddColumnMigration) Column(col *Column) *AddColumnMigration {
m.column = col
return m
}
func (m *AddColumnMigration) Sql(dialect Dialect) string {
return dialect.AddColumnSql(m.tableName, m.column)
}
type AddIndexMigration struct {
MigrationBase
tableName string
index *Index
}
func NewAddIndexMigration(table Table, index *Index) *AddIndexMigration {
return &AddIndexMigration{tableName: table.Name, index: index}
}
func (m *AddIndexMigration) Table(tableName string) *AddIndexMigration {
m.tableName = tableName
return m
}
func (m *AddIndexMigration) Sql(dialect Dialect) string {
return dialect.CreateIndexSql(m.tableName, m.index)
}
type DropIndexMigration struct {
MigrationBase
tableName string
index *Index
}
func NewDropIndexMigration(table Table, index *Index) *DropIndexMigration {
return &DropIndexMigration{tableName: table.Name, index: index}
}
func (m *DropIndexMigration) Sql(dialect Dialect) string {
if m.index.Name == "" {
m.index.Name = fmt.Sprintf("%s", strings.Join(m.index.Cols, "_"))
}
return dialect.DropIndexSql(m.tableName, m.index)
}
type AddTableMigration struct {
MigrationBase
table Table
}
func NewAddTableMigration(table Table) *AddTableMigration {
for _, col := range table.Columns {
if col.IsPrimaryKey {
table.PrimaryKeys = append(table.PrimaryKeys, col.Name)
}
}
return &AddTableMigration{table: table}
}
func (m *AddTableMigration) Sql(d Dialect) string {
return d.CreateTableSql(&m.table)
}
type DropTableMigration struct {
MigrationBase
tableName string
}
func NewDropTableMigration(tableName string) *DropTableMigration {
return &DropTableMigration{tableName: tableName}
}
func (m *DropTableMigration) Sql(d Dialect) string {
return d.DropTable(m.tableName)
}
type RenameTableMigration struct {
MigrationBase
oldName string
newName string
}
func NewRenameTableMigration(oldName string, newName string) *RenameTableMigration {
return &RenameTableMigration{oldName: oldName, newName: newName}
}
func (m *RenameTableMigration) IfTableExists(tableName string) *RenameTableMigration {
m.Condition = &IfTableExistsCondition{TableName: tableName}
return m
}
func (m *RenameTableMigration) Rename(oldName string, newName string) *RenameTableMigration {
m.oldName = oldName
m.newName = newName
return m
}
func (m *RenameTableMigration) Sql(d Dialect) string {
return d.RenameTable(m.oldName, m.newName)
}
type CopyTableDataMigration struct {
MigrationBase
sourceTable string
targetTable string
sourceCols []string
targetCols []string
colMap map[string]string
}
func NewCopyTableDataMigration(targetTable string, sourceTable string, colMap map[string]string) *CopyTableDataMigration {
m := &CopyTableDataMigration{sourceTable: sourceTable, targetTable: targetTable}
for key, value := range colMap {
m.targetCols = append(m.targetCols, key)
m.sourceCols = append(m.sourceCols, value)
}
return m
}
func (m *CopyTableDataMigration) IfTableExists(tableName string) *CopyTableDataMigration {
m.Condition = &IfTableExistsCondition{TableName: tableName}
return m
}
func (m *CopyTableDataMigration) Sql(d Dialect) string {
return d.CopyTableData(m.sourceTable, m.targetTable, m.sourceCols, m.targetCols)
}
+16 -5
View File
@@ -5,9 +5,9 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/go-xorm/xorm"
"github.com/grafana/grafana/pkg/log"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
"github.com/grafana/grafana/pkg/log"
)
type Migrator struct {
@@ -70,7 +70,7 @@ func (mg *Migrator) GetMigrationLog() (map[string]MigrationLog, error) {
func (mg *Migrator) Start() error {
if mg.LogLevel <= log.INFO {
log.Info("Migrator:: Starting DB migration")
log.Info("Migrator: Starting DB migration")
}
logMap, err := mg.GetMigrationLog()
@@ -82,7 +82,7 @@ func (mg *Migrator) Start() error {
_, exists := logMap[m.Id()]
if exists {
if mg.LogLevel <= log.DEBUG {
log.Debug("Migrator:: Skipping migration: %v, Already executed", m.Id())
log.Debug("Migrator: Skipping migration: %v, Already executed", m.Id())
}
continue
}
@@ -114,13 +114,24 @@ func (mg *Migrator) Start() error {
func (mg *Migrator) exec(m Migration) error {
if mg.LogLevel <= log.INFO {
log.Info("Migrator::exec migration id: %v", m.Id())
log.Info("Migrator: exec migration id: %v", m.Id())
}
err := mg.inTransaction(func(sess *xorm.Session) error {
condition := m.GetCondition()
if condition != nil {
sql, args := condition.Sql(mg.dialect)
results, err := sess.Query(sql, args...)
if err != nil || len(results) == 0 {
log.Info("Migrator: skipping migration id: %v, condition not fulfilled", m.Id())
return sess.Rollback()
}
}
_, err := sess.Exec(m.Sql(mg.dialect))
if err != nil {
log.Error(3, "Migrator::exec FAILED migration id: %v, err: %v", m.Id(), err)
log.Error(3, "Migrator: exec FAILED migration id: %v, err: %v", m.Id(), err)
return err
}
return nil
@@ -1,6 +1,9 @@
package migrator
import "strconv"
import (
"fmt"
"strconv"
)
type Postgres struct {
BaseDialect
@@ -84,3 +87,9 @@ func (db *Postgres) TableCheckSql(tableName string) (string, []interface{}) {
sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?"
return sql, args
}
func (db *Postgres) DropIndexSql(tableName string, index *Index) string {
quote := db.Quote
idxName := index.XName(tableName)
return fmt.Sprintf("DROP INDEX %v", quote(idxName))
}
@@ -1,5 +1,7 @@
package migrator
import "fmt"
type Sqlite3 struct {
BaseDialect
}
@@ -57,3 +59,10 @@ func (db *Sqlite3) TableCheckSql(tableName string) (string, []interface{}) {
args := []interface{}{tableName}
return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args
}
func (db *Sqlite3) DropIndexSql(tableName string, index *Index) string {
quote := db.Quote
//var unique string
idxName := index.XName(tableName)
return fmt.Sprintf("DROP INDEX %v", quote(idxName))
}
+22
View File
@@ -1,5 +1,10 @@
package migrator
import (
"fmt"
"strings"
)
const (
POSTGRES = "postgres"
SQLITE = "sqlite3"
@@ -10,6 +15,7 @@ type Migration interface {
Sql(dialect Dialect) string
Id() string
SetId(string)
GetCondition() MigrationCondition
}
type SQLType string
@@ -24,6 +30,7 @@ type Table struct {
Name string
Columns []*Column
PrimaryKeys []string
Indices []*Index
}
const (
@@ -37,6 +44,21 @@ type Index struct {
Cols []string
}
func (index *Index) XName(tableName string) string {
if index.Name == "" {
index.Name = fmt.Sprintf("%s", strings.Join(index.Cols, "_"))
}
if !strings.HasPrefix(index.Name, "UQE_") &&
!strings.HasPrefix(index.Name, "IDX_") {
if index.Type == UniqueIndex {
return fmt.Sprintf("UQE_%v_%v", tableName, index.Name)
}
return fmt.Sprintf("IDX_%v_%v", tableName, index.Name)
}
return index.Name
}
var (
DB_Bit = "BIT"
DB_TinyInt = "TINYINT"
+134
View File
@@ -0,0 +1,134 @@
package sqlstore
import (
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/events"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
)
func init() {
bus.AddHandler("sql", GetOrgById)
bus.AddHandler("sql", CreateOrg)
bus.AddHandler("sql", UpdateOrg)
bus.AddHandler("sql", GetOrgByName)
bus.AddHandler("sql", GetOrgList)
bus.AddHandler("sql", DeleteOrg)
}
func GetOrgList(query *m.GetOrgListQuery) error {
return x.Find(&query.Result)
}
func GetOrgById(query *m.GetOrgByIdQuery) error {
var org m.Org
exists, err := x.Id(query.Id).Get(&org)
if err != nil {
return err
}
if !exists {
return m.ErrOrgNotFound
}
query.Result = &org
return nil
}
func GetOrgByName(query *m.GetOrgByNameQuery) error {
var org m.Org
exists, err := x.Where("name=?", query.Name).Get(&org)
if err != nil {
return err
}
if !exists {
return m.ErrOrgNotFound
}
query.Result = &org
return nil
}
func CreateOrg(cmd *m.CreateOrgCommand) error {
return inTransaction2(func(sess *session) error {
org := m.Org{
Name: cmd.Name,
Created: time.Now(),
Updated: time.Now(),
}
if _, err := sess.Insert(&org); err != nil {
return err
}
user := m.OrgUser{
OrgId: org.Id,
UserId: cmd.UserId,
Role: m.ROLE_ADMIN,
Created: time.Now(),
Updated: time.Now(),
}
_, err := sess.Insert(&user)
cmd.Result = org
sess.publishAfterCommit(&events.OrgCreated{
Timestamp: org.Created,
Id: org.Id,
Name: org.Name,
})
return err
})
}
func UpdateOrg(cmd *m.UpdateOrgCommand) error {
return inTransaction2(func(sess *session) error {
org := m.Org{
Name: cmd.Name,
Updated: time.Now(),
}
if _, err := sess.Id(cmd.OrgId).Update(&org); err != nil {
return err
}
sess.publishAfterCommit(&events.OrgUpdated{
Timestamp: org.Updated,
Id: org.Id,
Name: org.Name,
})
return nil
})
}
func DeleteOrg(cmd *m.DeleteOrgCommand) error {
return inTransaction2(func(sess *session) error {
deletes := []string{
"DELETE FROM star WHERE EXISTS (SELECT 1 FROM dashboard WHERE org_id = ?)",
"DELETE FROM dashboard_tag WHERE EXISTS (SELECT 1 FROM dashboard WHERE org_id = ?)",
"DELETE FROM dashboard WHERE org_id = ?",
"DELETE FROM api_key WHERE org_id = ?",
"DELETE FROM data_source WHERE org_id = ?",
"DELETE FROM org_user WHERE org_id = ?",
"DELETE FROM org WHERE id = ?",
}
for _, sql := range deletes {
log.Trace(sql)
_, err := sess.Exec(sql, cmd.Id)
if err != nil {
return err
}
}
return nil
})
}
@@ -14,12 +14,12 @@ func TestAccountDataAccess(t *testing.T) {
Convey("Testing Account DB Access", t, func() {
InitTestDB(t)
Convey("Given single account mode", func() {
setting.SingleAccountMode = true
setting.DefaultAccountName = "test"
setting.DefaultAccountRole = "Viewer"
Convey("Given single org mode", func() {
setting.SingleOrgMode = true
setting.DefaultOrgName = "test"
setting.DefaultOrgRole = "Viewer"
Convey("Users should be added to default account", func() {
Convey("Users should be added to default organization", func() {
ac1cmd := m.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"}
ac2cmd := m.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name"}
@@ -28,20 +28,20 @@ func TestAccountDataAccess(t *testing.T) {
err = CreateUser(&ac2cmd)
So(err, ShouldBeNil)
q1 := m.GetUserAccountsQuery{UserId: ac1cmd.Result.Id}
q2 := m.GetUserAccountsQuery{UserId: ac2cmd.Result.Id}
GetUserAccounts(&q1)
GetUserAccounts(&q2)
q1 := m.GetUserOrgListQuery{UserId: ac1cmd.Result.Id}
q2 := m.GetUserOrgListQuery{UserId: ac2cmd.Result.Id}
GetUserOrgList(&q1)
GetUserOrgList(&q2)
So(q1.Result[0].AccountId, ShouldEqual, q2.Result[0].AccountId)
So(q1.Result[0].OrgId, ShouldEqual, q2.Result[0].OrgId)
So(q1.Result[0].Role, ShouldEqual, "Viewer")
})
})
Convey("Given two saved users", func() {
setting.SingleAccountMode = false
setting.SingleOrgMode = false
setting.DefaultOrgName = "test"
setting.DefaultAccountName = "test"
ac1cmd := m.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"}
ac2cmd := m.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name", IsAdmin: true}
@@ -70,14 +70,14 @@ func TestAccountDataAccess(t *testing.T) {
So(query.Result[1].Email, ShouldEqual, "ac2@test.com")
})
Convey("Given an added account user", func() {
cmd := m.AddAccountUserCommand{
AccountId: ac1.AccountId,
UserId: ac2.Id,
Role: m.ROLE_VIEWER,
Convey("Given an added org user", func() {
cmd := m.AddOrgUserCommand{
OrgId: ac1.OrgId,
UserId: ac2.Id,
Role: m.ROLE_VIEWER,
}
err := AddAccountUser(&cmd)
err := AddOrgUser(&cmd)
Convey("Should have been saved without error", func() {
So(err, ShouldBeNil)
})
@@ -88,54 +88,54 @@ func TestAccountDataAccess(t *testing.T) {
So(err, ShouldBeNil)
So(query.Result.Email, ShouldEqual, "ac2@test.com")
So(query.Result.AccountId, ShouldEqual, ac2.AccountId)
So(query.Result.OrgId, ShouldEqual, ac2.OrgId)
So(query.Result.Name, ShouldEqual, "ac2 name")
So(query.Result.Login, ShouldEqual, "ac2")
So(query.Result.AccountRole, ShouldEqual, "Admin")
So(query.Result.AccountName, ShouldEqual, "ac2@test.com")
So(query.Result.OrgRole, ShouldEqual, "Admin")
So(query.Result.OrgName, ShouldEqual, "ac2@test.com")
So(query.Result.IsGrafanaAdmin, ShouldBeTrue)
})
Convey("Can get user accounts", func() {
query := m.GetUserAccountsQuery{UserId: ac2.Id}
err := GetUserAccounts(&query)
Convey("Can get user organizations", func() {
query := m.GetUserOrgListQuery{UserId: ac2.Id}
err := GetUserOrgList(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
})
Convey("Can get account users", func() {
query := m.GetAccountUsersQuery{AccountId: ac1.AccountId}
err := GetAccountUsers(&query)
Convey("Can get organization users", func() {
query := m.GetOrgUsersQuery{OrgId: ac1.OrgId}
err := GetOrgUsers(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].Role, ShouldEqual, "Admin")
})
Convey("Can set using account", func() {
cmd := m.SetUsingAccountCommand{UserId: ac2.Id, AccountId: ac1.Id}
err := SetUsingAccount(&cmd)
Convey("Can set using org", func() {
cmd := m.SetUsingOrgCommand{UserId: ac2.Id, OrgId: ac1.Id}
err := SetUsingOrg(&cmd)
So(err, ShouldBeNil)
Convey("SignedInUserQuery with a different account", func() {
Convey("SignedInUserQuery with a different org", func() {
query := m.GetSignedInUserQuery{UserId: ac2.Id}
err := GetSignedInUser(&query)
So(err, ShouldBeNil)
So(query.Result.AccountId, ShouldEqual, ac1.Id)
So(query.Result.OrgId, ShouldEqual, ac1.Id)
So(query.Result.Email, ShouldEqual, "ac2@test.com")
So(query.Result.Name, ShouldEqual, "ac2 name")
So(query.Result.Login, ShouldEqual, "ac2")
So(query.Result.AccountName, ShouldEqual, "ac1@test.com")
So(query.Result.AccountRole, ShouldEqual, "Viewer")
So(query.Result.OrgName, ShouldEqual, "ac1@test.com")
So(query.Result.OrgRole, ShouldEqual, "Viewer")
})
})
Convey("Cannot delete last admin account user", func() {
cmd := m.RemoveAccountUserCommand{AccountId: ac1.AccountId, UserId: ac1.Id}
err := RemoveAccountUser(&cmd)
So(err, ShouldEqual, m.ErrLastAccountAdmin)
cmd := m.RemoveOrgUserCommand{OrgId: ac1.OrgId, UserId: ac1.Id}
err := RemoveOrgUser(&cmd)
So(err, ShouldEqual, m.ErrLastOrgAdmin)
})
})
})
+67
View File
@@ -0,0 +1,67 @@
package sqlstore
import (
"fmt"
"time"
"github.com/go-xorm/xorm"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
)
func init() {
bus.AddHandler("sql", AddOrgUser)
bus.AddHandler("sql", RemoveOrgUser)
bus.AddHandler("sql", GetOrgUsers)
}
func AddOrgUser(cmd *m.AddOrgUserCommand) error {
return inTransaction(func(sess *xorm.Session) error {
entity := m.OrgUser{
OrgId: cmd.OrgId,
UserId: cmd.UserId,
Role: cmd.Role,
Created: time.Now(),
Updated: time.Now(),
}
_, err := sess.Insert(&entity)
return err
})
}
func GetOrgUsers(query *m.GetOrgUsersQuery) error {
query.Result = make([]*m.OrgUserDTO, 0)
sess := x.Table("org_user")
sess.Join("INNER", "user", fmt.Sprintf("org_user.user_id=%s.id", x.Dialect().Quote("user")))
sess.Where("org_user.org_id=?", query.OrgId)
sess.Cols("org_user.org_id", "org_user.user_id", "user.email", "user.login", "org_user.role")
sess.Asc("user.email", "user.login")
err := sess.Find(&query.Result)
return err
}
func RemoveOrgUser(cmd *m.RemoveOrgUserCommand) error {
return inTransaction(func(sess *xorm.Session) error {
var rawSql = "DELETE FROM org_user WHERE org_id=? and user_id=?"
_, err := sess.Exec(rawSql, cmd.OrgId, cmd.UserId)
if err != nil {
return err
}
// validate that there is an admin user left
res, err := sess.Query("SELECT 1 from org_user WHERE org_id=? and role='Admin'", cmd.OrgId)
if err != nil {
return err
}
if len(res) == 0 {
return m.ErrLastOrgAdmin
}
return err
})
}
+2 -1
View File
@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/setting"
@@ -73,7 +74,7 @@ func SetEngine(engine *xorm.Engine, enableLog bool) (err error) {
migrator := migrator.NewMigrator(x)
migrator.LogLevel = log.INFO
addMigrations(migrator)
migrations.AddMigrations(migrator)
if err := migrator.Start(); err != nil {
return fmt.Errorf("Sqlstore::Migration failed err: %v\n", err)
+104 -47
View File
@@ -1,6 +1,7 @@
package sqlstore
import (
"fmt"
"strings"
"time"
@@ -15,59 +16,64 @@ import (
func init() {
bus.AddHandler("sql", CreateUser)
bus.AddHandler("sql", GetUserById)
bus.AddHandler("sql", UpdateUser)
bus.AddHandler("sql", ChangeUserPassword)
bus.AddHandler("sql", GetUserByLogin)
bus.AddHandler("sql", SetUsingAccount)
bus.AddHandler("sql", SetUsingOrg)
bus.AddHandler("sql", GetUserInfo)
bus.AddHandler("sql", GetSignedInUser)
bus.AddHandler("sql", SearchUsers)
bus.AddHandler("sql", GetUserAccounts)
bus.AddHandler("sql", GetUserOrgList)
bus.AddHandler("sql", DeleteUser)
bus.AddHandler("sql", SetUsingOrg)
bus.AddHandler("sql", UpdateUserPermissions)
}
func getAccountIdForNewUser(userEmail string, sess *session) (int64, error) {
var account m.Account
func getOrgIdForNewUser(userEmail string, sess *session) (int64, error) {
var org m.Org
if setting.SingleAccountMode {
has, err := sess.Where("name=?", setting.DefaultAccountName).Get(&account)
if setting.SingleOrgMode {
has, err := sess.Where("name=?", setting.DefaultOrgName).Get(&org)
if err != nil {
return 0, err
}
if has {
return account.Id, nil
return org.Id, nil
} else {
account.Name = setting.DefaultAccountName
org.Name = setting.DefaultOrgName
}
} else {
account.Name = userEmail
org.Name = userEmail
}
account.Created = time.Now()
account.Updated = time.Now()
org.Created = time.Now()
org.Updated = time.Now()
if _, err := sess.Insert(&account); err != nil {
if _, err := sess.Insert(&org); err != nil {
return 0, err
}
return account.Id, nil
return org.Id, nil
}
func CreateUser(cmd *m.CreateUserCommand) error {
return inTransaction2(func(sess *session) error {
accountId, err := getAccountIdForNewUser(cmd.Email, sess)
orgId, err := getOrgIdForNewUser(cmd.Email, sess)
if err != nil {
return err
}
// create user
user := m.User{
Email: cmd.Email,
Name: cmd.Name,
Login: cmd.Login,
Company: cmd.Company,
IsAdmin: cmd.IsAdmin,
AccountId: accountId,
Created: time.Now(),
Updated: time.Now(),
Email: cmd.Email,
Name: cmd.Name,
Login: cmd.Login,
Company: cmd.Company,
IsAdmin: cmd.IsAdmin,
OrgId: orgId,
Created: time.Now(),
Updated: time.Now(),
}
if len(cmd.Password) > 0 {
@@ -82,20 +88,20 @@ func CreateUser(cmd *m.CreateUserCommand) error {
return err
}
// create account user link
accountUser := m.AccountUser{
AccountId: accountId,
UserId: user.Id,
Role: m.ROLE_ADMIN,
Created: time.Now(),
Updated: time.Now(),
// create org user link
orgUser := m.OrgUser{
OrgId: orgId,
UserId: user.Id,
Role: m.ROLE_ADMIN,
Created: time.Now(),
Updated: time.Now(),
}
if setting.SingleAccountMode && !user.IsAdmin {
accountUser.Role = m.RoleType(setting.DefaultAccountRole)
if setting.SingleOrgMode && !user.IsAdmin {
orgUser.Role = m.RoleType(setting.DefaultOrgRole)
}
if _, err = sess.Insert(&accountUser); err != nil {
if _, err = sess.Insert(&orgUser); err != nil {
return err
}
@@ -112,16 +118,31 @@ func CreateUser(cmd *m.CreateUserCommand) error {
})
}
func GetUserById(query *m.GetUserByIdQuery) error {
user := new(m.User)
has, err := x.Id(query.Id).Get(user)
if err != nil {
return err
} else if has == false {
return m.ErrUserNotFound
}
query.Result = user
return nil
}
func GetUserByLogin(query *m.GetUserByLoginQuery) error {
if query.LoginOrEmail == "" {
return m.ErrAccountNotFound
return m.ErrUserNotFound
}
user := new(m.User)
if strings.Contains(query.LoginOrEmail, "@") {
user = &m.User{Email: query.LoginOrEmail}
} else {
user = &m.User{Login: strings.ToLower(query.LoginOrEmail)}
user = &m.User{Login: query.LoginOrEmail}
}
has, err := x.Get(user)
@@ -163,12 +184,28 @@ func UpdateUser(cmd *m.UpdateUserCommand) error {
})
}
func SetUsingAccount(cmd *m.SetUsingAccountCommand) error {
func ChangeUserPassword(cmd *m.ChangeUserPasswordCommand) error {
return inTransaction2(func(sess *session) error {
user := m.User{
Password: cmd.NewPassword,
Updated: time.Now(),
}
if _, err := sess.Id(cmd.UserId).Update(&user); err != nil {
return err
}
return nil
})
}
func SetUsingOrg(cmd *m.SetUsingOrgCommand) error {
return inTransaction(func(sess *xorm.Session) error {
user := m.User{}
sess.Id(cmd.UserId).Get(&user)
user.AccountId = cmd.AccountId
user.OrgId = cmd.OrgId
_, err := sess.Id(user.Id).Update(&user)
return err
})
@@ -193,12 +230,12 @@ func GetUserInfo(query *m.GetUserInfoQuery) error {
return err
}
func GetUserAccounts(query *m.GetUserAccountsQuery) error {
query.Result = make([]*m.UserAccountDTO, 0)
sess := x.Table("account_user")
sess.Join("INNER", "account", "account_user.account_id=account.id")
sess.Where("account_user.user_id=?", query.UserId)
sess.Cols("account.name", "account_user.role", "account_user.account_id")
func GetUserOrgList(query *m.GetUserOrgListQuery) error {
query.Result = make([]*m.UserOrgDTO, 0)
sess := x.Table("org_user")
sess.Join("INNER", "org", "org_user.org_id=org.id")
sess.Where("org_user.user_id=?", query.UserId)
sess.Cols("org.name", "org_user.role", "org_user.org_id")
err := sess.Find(&query.Result)
return err
}
@@ -210,12 +247,12 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error {
u.email as email,
u.login as login,
u.name as name,
account.name as account_name,
account_user.role as account_role,
account.id as account_id
org.name as org_name,
org_user.role as org_role,
org.id as org_id
FROM ` + dialect.Quote("user") + ` as u
LEFT OUTER JOIN account_user on account_user.account_id = u.account_id and account_user.user_id = u.id
LEFT OUTER JOIN account on account.id = u.account_id
LEFT OUTER JOIN org_user on org_user.org_id = u.org_id and org_user.user_id = u.id
LEFT OUTER JOIN org on org.id = u.org_id
WHERE u.id=?`
var user m.SignedInUser
@@ -240,3 +277,23 @@ func SearchUsers(query *m.SearchUsersQuery) error {
err := sess.Find(&query.Result)
return err
}
func DeleteUser(cmd *m.DeleteUserCommand) error {
return inTransaction(func(sess *xorm.Session) error {
var rawSql = fmt.Sprintf("DELETE FROM %s WHERE id=?", x.Dialect().Quote("user"))
_, err := sess.Exec(rawSql, cmd.UserId)
return err
})
}
func UpdateUserPermissions(cmd *m.UpdateUserPermissionsCommand) error {
return inTransaction(func(sess *xorm.Session) error {
user := m.User{}
sess.Id(cmd.UserId).Get(&user)
user.IsAdmin = cmd.IsGrafanaAdmin
sess.UseBool("is_admin")
_, err := sess.Id(user.Id).Update(&user)
return err
})
}
+37 -25
View File
@@ -4,6 +4,7 @@
package setting
import (
"fmt"
"net/url"
"os"
"path"
@@ -65,18 +66,18 @@ var (
CookieRememberName string
DisableUserSignUp bool
// single account
SingleAccountMode bool
DefaultAccountName string
DefaultAccountRole string
// single organization
SingleOrgMode bool
DefaultOrgName string
DefaultOrgRole string
// Http auth
AdminUser string
AdminPassword string
AnonymousEnabled bool
AnonymousAccountName string
AnonymousAccountRole string
AnonymousEnabled bool
AnonymousOrgName string
AnonymousOrgRole string
// Session settings.
SessionOptions session.Options
@@ -98,15 +99,10 @@ var (
func init() {
IsWindows = runtime.GOOS == "windows"
log.NewLogger(0, "console", `{"level": 0}`)
}
func getWorkDir() string {
p, _ := filepath.Abs(".")
return p
WorkDir, _ = filepath.Abs(".")
}
func findConfigFiles() []string {
WorkDir = getWorkDir()
ConfRootPath = path.Join(WorkDir, "conf")
filenames := make([]string, 0)
@@ -152,10 +148,29 @@ func ToAbsUrl(relativeUrl string) string {
return AppUrl + relativeUrl
}
func NewConfigContext() {
func loadEnvVariableOverrides() {
for _, section := range Cfg.Sections() {
for _, key := range section.Keys() {
sectionName := strings.ToUpper(strings.Replace(section.Name(), ".", "_", -1))
keyName := strings.ToUpper(strings.Replace(key.Name(), ".", "_", -1))
envKey := fmt.Sprintf("GF_%s_%s", sectionName, keyName)
envValue := os.Getenv(envKey)
if len(envValue) > 0 {
log.Info("Setting: ENV override found: %s", envKey)
key.SetValue(envValue)
}
}
}
}
func NewConfigContext(config string) {
configFiles := findConfigFiles()
//log.Info("Loading config files: %v", configFiles)
if config != "" {
configFiles = append(configFiles, config)
}
var err error
for i, file := range configFiles {
@@ -170,6 +185,8 @@ func NewConfigContext() {
}
}
loadEnvVariableOverrides()
AppName = Cfg.Section("").Key("app_name").MustString("Grafana")
Env = Cfg.Section("").Key("app_mode").MustString("development")
@@ -187,11 +204,6 @@ func NewConfigContext() {
HttpAddr = server.Key("http_addr").MustString("0.0.0.0")
HttpPort = server.Key("http_port").MustString("3000")
port := os.Getenv("PORT")
if port != "" {
HttpPort = port
}
StaticRootPath = server.Key("static_root_path").MustString(path.Join(WorkDir, "webapp"))
RouterLogging = server.Key("router_logging").MustBool(false)
EnableGzip = server.Key("enable_gzip").MustBool(false)
@@ -208,14 +220,14 @@ func NewConfigContext() {
AdminPassword = security.Key("admin_password").String()
// single account
SingleAccountMode = Cfg.Section("account.single").Key("enabled").MustBool(false)
DefaultAccountName = Cfg.Section("account.single").Key("account_name").MustString("main")
DefaultAccountRole = Cfg.Section("account.single").Key("default_role").In("Editor", []string{"Editor", "Admin", "Viewer"})
SingleOrgMode = Cfg.Section("organization.single").Key("enabled").MustBool(false)
DefaultOrgName = Cfg.Section("organization.single").Key("org_name").MustString("main")
DefaultOrgRole = Cfg.Section("organization.single").Key("default_role").In("Editor", []string{"Editor", "Admin", "Viewer"})
// anonymous access
AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)
AnonymousAccountName = Cfg.Section("auth.anonymous").Key("account_name").String()
AnonymousAccountRole = Cfg.Section("auth.anonymous").Key("account_role").String()
AnonymousOrgName = Cfg.Section("auth.anonymous").Key("org_name").String()
AnonymousOrgRole = Cfg.Section("auth.anonymous").Key("org_role").String()
// PhantomJS rendering
ImagesDir = "data/png"
+32
View File
@@ -0,0 +1,32 @@
package setting
import (
"os"
"path/filepath"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestLoadingSettings(t *testing.T) {
WorkDir, _ = filepath.Abs("../../")
Convey("Testing loading settings from ini file", t, func() {
Convey("Given the default ini files", func() {
NewConfigContext("")
So(AppName, ShouldEqual, "Grafana")
So(AdminUser, ShouldEqual, "admin")
})
Convey("Should be able to override via environment variables", func() {
os.Setenv("GF_SECURITY_ADMIN_USER", "superduper")
NewConfigContext("")
So(AdminUser, ShouldEqual, "superduper")
})
})
}