setting.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. // Copyright 2014 Unknwon
  2. // Copyright 2014 Torkel Ödegaard
  3. package setting
  4. import (
  5. "bytes"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "net/url"
  10. "os"
  11. "path"
  12. "path/filepath"
  13. "regexp"
  14. "runtime"
  15. "strings"
  16. "github.com/macaron-contrib/session"
  17. "gopkg.in/ini.v1"
  18. "github.com/grafana/grafana/pkg/log"
  19. "github.com/grafana/grafana/pkg/util"
  20. )
  21. type Scheme string
  22. const (
  23. HTTP Scheme = "http"
  24. HTTPS Scheme = "https"
  25. )
  26. const (
  27. DEV string = "development"
  28. PROD string = "production"
  29. TEST string = "test"
  30. )
  31. var (
  32. // App settings.
  33. Env string = DEV
  34. AppUrl string
  35. AppSubUrl string
  36. // build
  37. BuildVersion string
  38. BuildCommit string
  39. BuildStamp int64
  40. // Paths
  41. LogsPath string
  42. HomePath string
  43. DataPath string
  44. PluginsPath string
  45. // Log settings.
  46. LogModes []string
  47. LogConfigs []util.DynMap
  48. // Http server options
  49. Protocol Scheme
  50. Domain string
  51. HttpAddr, HttpPort string
  52. SshPort int
  53. CertFile, KeyFile string
  54. RouterLogging bool
  55. StaticRootPath string
  56. EnableGzip bool
  57. EnforceDomain bool
  58. // Security settings.
  59. SecretKey string
  60. LogInRememberDays int
  61. CookieUserName string
  62. CookieRememberName string
  63. DisableGravatar bool
  64. EmailCodeValidMinutes int
  65. DataProxyWhiteList map[string]bool
  66. // Snapshots
  67. ExternalSnapshotUrl string
  68. ExternalSnapshotName string
  69. ExternalEnabled bool
  70. // User settings
  71. AllowUserSignUp bool
  72. AllowUserOrgCreate bool
  73. AutoAssignOrg bool
  74. AutoAssignOrgRole string
  75. VerifyEmailEnabled bool
  76. LoginHint string
  77. // Http auth
  78. AdminUser string
  79. AdminPassword string
  80. AnonymousEnabled bool
  81. AnonymousOrgName string
  82. AnonymousOrgRole string
  83. // Auth proxy settings
  84. AuthProxyEnabled bool
  85. AuthProxyHeaderName string
  86. AuthProxyHeaderProperty string
  87. AuthProxyAutoSignUp bool
  88. // Basic Auth
  89. BasicAuthEnabled bool
  90. // Session settings.
  91. SessionOptions session.Options
  92. // Global setting objects.
  93. Cfg *ini.File
  94. ConfRootPath string
  95. IsWindows bool
  96. // PhantomJs Rendering
  97. ImagesDir string
  98. PhantomDir string
  99. // for logging purposes
  100. configFiles []string
  101. appliedCommandLineProperties []string
  102. appliedEnvOverrides []string
  103. ReportingEnabled bool
  104. GoogleAnalyticsId string
  105. GoogleTagManagerId string
  106. // LDAP
  107. LdapEnabled bool
  108. LdapConfigFile string
  109. // SMTP email settings
  110. Smtp SmtpSettings
  111. // QUOTA
  112. Quota QuotaSettings
  113. )
  114. type CommandLineArgs struct {
  115. Config string
  116. HomePath string
  117. Args []string
  118. }
  119. func init() {
  120. IsWindows = runtime.GOOS == "windows"
  121. log.NewLogger(0, "console", `{"level": 0, "formatting":true}`)
  122. }
  123. func parseAppUrlAndSubUrl(section *ini.Section) (string, string) {
  124. appUrl := section.Key("root_url").MustString("http://localhost:3000/")
  125. if appUrl[len(appUrl)-1] != '/' {
  126. appUrl += "/"
  127. }
  128. // Check if has app suburl.
  129. url, err := url.Parse(appUrl)
  130. if err != nil {
  131. log.Fatal(4, "Invalid root_url(%s): %s", appUrl, err)
  132. }
  133. appSubUrl := strings.TrimSuffix(url.Path, "/")
  134. return appUrl, appSubUrl
  135. }
  136. func ToAbsUrl(relativeUrl string) string {
  137. return AppUrl + relativeUrl
  138. }
  139. func applyEnvVariableOverrides() {
  140. appliedEnvOverrides = make([]string, 0)
  141. for _, section := range Cfg.Sections() {
  142. for _, key := range section.Keys() {
  143. sectionName := strings.ToUpper(strings.Replace(section.Name(), ".", "_", -1))
  144. keyName := strings.ToUpper(strings.Replace(key.Name(), ".", "_", -1))
  145. envKey := fmt.Sprintf("GF_%s_%s", sectionName, keyName)
  146. envValue := os.Getenv(envKey)
  147. if len(envValue) > 0 {
  148. key.SetValue(envValue)
  149. if strings.Contains(envKey, "PASSWORD") {
  150. envValue = "*********"
  151. }
  152. appliedEnvOverrides = append(appliedEnvOverrides, fmt.Sprintf("%s=%s", envKey, envValue))
  153. }
  154. }
  155. }
  156. }
  157. func applyCommandLineDefaultProperties(props map[string]string) {
  158. appliedCommandLineProperties = make([]string, 0)
  159. for _, section := range Cfg.Sections() {
  160. for _, key := range section.Keys() {
  161. keyString := fmt.Sprintf("default.%s.%s", section.Name(), key.Name())
  162. value, exists := props[keyString]
  163. if exists {
  164. key.SetValue(value)
  165. if strings.Contains(keyString, "password") {
  166. value = "*********"
  167. }
  168. appliedCommandLineProperties = append(appliedCommandLineProperties, fmt.Sprintf("%s=%s", keyString, value))
  169. }
  170. }
  171. }
  172. }
  173. func applyCommandLineProperties(props map[string]string) {
  174. for _, section := range Cfg.Sections() {
  175. for _, key := range section.Keys() {
  176. keyString := fmt.Sprintf("%s.%s", section.Name(), key.Name())
  177. value, exists := props[keyString]
  178. if exists {
  179. key.SetValue(value)
  180. appliedCommandLineProperties = append(appliedCommandLineProperties, fmt.Sprintf("%s=%s", keyString, value))
  181. }
  182. }
  183. }
  184. }
  185. func getCommandLineProperties(args []string) map[string]string {
  186. props := make(map[string]string)
  187. for _, arg := range args {
  188. if !strings.HasPrefix(arg, "cfg:") {
  189. continue
  190. }
  191. trimmed := strings.TrimPrefix(arg, "cfg:")
  192. parts := strings.Split(trimmed, "=")
  193. if len(parts) != 2 {
  194. log.Fatal(3, "Invalid command line argument", arg)
  195. return nil
  196. }
  197. props[parts[0]] = parts[1]
  198. }
  199. return props
  200. }
  201. func makeAbsolute(path string, root string) string {
  202. if filepath.IsAbs(path) {
  203. return path
  204. }
  205. return filepath.Join(root, path)
  206. }
  207. func evalEnvVarExpression(value string) string {
  208. regex := regexp.MustCompile(`\${(\w+)}`)
  209. return regex.ReplaceAllStringFunc(value, func(envVar string) string {
  210. envVar = strings.TrimPrefix(envVar, "${")
  211. envVar = strings.TrimSuffix(envVar, "}")
  212. envValue := os.Getenv(envVar)
  213. return envValue
  214. })
  215. }
  216. func evalConfigValues() {
  217. for _, section := range Cfg.Sections() {
  218. for _, key := range section.Keys() {
  219. key.SetValue(evalEnvVarExpression(key.Value()))
  220. }
  221. }
  222. }
  223. func loadSpecifedConfigFile(configFile string) {
  224. if configFile == "" {
  225. configFile = filepath.Join(HomePath, "conf/custom.ini")
  226. // return without error if custom file does not exist
  227. if !pathExists(configFile) {
  228. return
  229. }
  230. }
  231. userConfig, err := ini.Load(configFile)
  232. userConfig.BlockMode = false
  233. if err != nil {
  234. log.Fatal(3, "Failed to parse %v, %v", configFile, err)
  235. }
  236. for _, section := range userConfig.Sections() {
  237. for _, key := range section.Keys() {
  238. if key.Value() == "" {
  239. continue
  240. }
  241. defaultSec, err := Cfg.GetSection(section.Name())
  242. if err != nil {
  243. defaultSec, _ = Cfg.NewSection(section.Name())
  244. }
  245. defaultKey, err := defaultSec.GetKey(key.Name())
  246. if err != nil {
  247. defaultKey, _ = defaultSec.NewKey(key.Name(), key.Value())
  248. }
  249. defaultKey.SetValue(key.Value())
  250. }
  251. }
  252. configFiles = append(configFiles, configFile)
  253. }
  254. func loadConfiguration(args *CommandLineArgs) {
  255. var err error
  256. // load config defaults
  257. defaultConfigFile := path.Join(HomePath, "conf/defaults.ini")
  258. configFiles = append(configFiles, defaultConfigFile)
  259. Cfg, err = ini.Load(defaultConfigFile)
  260. Cfg.BlockMode = false
  261. if err != nil {
  262. log.Fatal(3, "Failed to parse defaults.ini, %v", err)
  263. }
  264. // command line props
  265. commandLineProps := getCommandLineProperties(args.Args)
  266. // load default overrides
  267. applyCommandLineDefaultProperties(commandLineProps)
  268. // init logging before specific config so we can log errors from here on
  269. DataPath = makeAbsolute(Cfg.Section("paths").Key("data").String(), HomePath)
  270. initLogging(args)
  271. // load specified config file
  272. loadSpecifedConfigFile(args.Config)
  273. // apply environment overrides
  274. applyEnvVariableOverrides()
  275. // apply command line overrides
  276. applyCommandLineProperties(commandLineProps)
  277. // evaluate config values containing environment variables
  278. evalConfigValues()
  279. // update data path and logging config
  280. DataPath = makeAbsolute(Cfg.Section("paths").Key("data").String(), HomePath)
  281. initLogging(args)
  282. }
  283. func pathExists(path string) bool {
  284. _, err := os.Stat(path)
  285. if err == nil {
  286. return true
  287. }
  288. if os.IsNotExist(err) {
  289. return false
  290. }
  291. return false
  292. }
  293. func setHomePath(args *CommandLineArgs) {
  294. if args.HomePath != "" {
  295. HomePath = args.HomePath
  296. return
  297. }
  298. HomePath, _ = filepath.Abs(".")
  299. // check if homepath is correct
  300. if pathExists(filepath.Join(HomePath, "conf/defaults.ini")) {
  301. return
  302. }
  303. // try down one path
  304. if pathExists(filepath.Join(HomePath, "../conf/defaults.ini")) {
  305. HomePath = filepath.Join(HomePath, "../")
  306. }
  307. }
  308. var skipStaticRootValidation bool = false
  309. func validateStaticRootPath() error {
  310. if skipStaticRootValidation {
  311. return nil
  312. }
  313. if _, err := os.Stat(path.Join(StaticRootPath, "css")); err == nil {
  314. return nil
  315. }
  316. if _, err := os.Stat(StaticRootPath + "_gen/css"); err == nil {
  317. StaticRootPath = StaticRootPath + "_gen"
  318. return nil
  319. }
  320. return errors.New("Failed to detect generated css or javascript files in static root (%s), have you executed default grunt task?")
  321. }
  322. func NewConfigContext(args *CommandLineArgs) error {
  323. setHomePath(args)
  324. loadConfiguration(args)
  325. Env = Cfg.Section("").Key("app_mode").MustString("development")
  326. PluginsPath = Cfg.Section("paths").Key("plugins").String()
  327. server := Cfg.Section("server")
  328. AppUrl, AppSubUrl = parseAppUrlAndSubUrl(server)
  329. Protocol = HTTP
  330. if server.Key("protocol").MustString("http") == "https" {
  331. Protocol = HTTPS
  332. CertFile = server.Key("cert_file").String()
  333. KeyFile = server.Key("cert_key").String()
  334. }
  335. Domain = server.Key("domain").MustString("localhost")
  336. HttpAddr = server.Key("http_addr").MustString("0.0.0.0")
  337. HttpPort = server.Key("http_port").MustString("3000")
  338. RouterLogging = server.Key("router_logging").MustBool(false)
  339. EnableGzip = server.Key("enable_gzip").MustBool(false)
  340. EnforceDomain = server.Key("enforce_domain").MustBool(false)
  341. StaticRootPath = makeAbsolute(server.Key("static_root_path").String(), HomePath)
  342. if err := validateStaticRootPath(); err != nil {
  343. return err
  344. }
  345. // read security settings
  346. security := Cfg.Section("security")
  347. SecretKey = security.Key("secret_key").String()
  348. LogInRememberDays = security.Key("login_remember_days").MustInt()
  349. CookieUserName = security.Key("cookie_username").String()
  350. CookieRememberName = security.Key("cookie_remember_name").String()
  351. DisableGravatar = security.Key("disable_gravatar").MustBool(true)
  352. // read snapshots settings
  353. snapshots := Cfg.Section("snapshots")
  354. ExternalSnapshotUrl = snapshots.Key("external_snapshot_url").String()
  355. ExternalSnapshotName = snapshots.Key("external_snapshot_name").String()
  356. ExternalEnabled = snapshots.Key("external_enabled").MustBool(true)
  357. // read data source proxy white list
  358. DataProxyWhiteList = make(map[string]bool)
  359. for _, hostAndIp := range security.Key("data_source_proxy_whitelist").Strings(" ") {
  360. DataProxyWhiteList[hostAndIp] = true
  361. }
  362. // admin
  363. AdminUser = security.Key("admin_user").String()
  364. AdminPassword = security.Key("admin_password").String()
  365. users := Cfg.Section("users")
  366. AllowUserSignUp = users.Key("allow_sign_up").MustBool(true)
  367. AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true)
  368. AutoAssignOrg = users.Key("auto_assign_org").MustBool(true)
  369. AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Read Only Editor", "Viewer"})
  370. VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false)
  371. LoginHint = users.Key("login_hint").String()
  372. // anonymous access
  373. AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)
  374. AnonymousOrgName = Cfg.Section("auth.anonymous").Key("org_name").String()
  375. AnonymousOrgRole = Cfg.Section("auth.anonymous").Key("org_role").String()
  376. // auth proxy
  377. authProxy := Cfg.Section("auth.proxy")
  378. AuthProxyEnabled = authProxy.Key("enabled").MustBool(false)
  379. AuthProxyHeaderName = authProxy.Key("header_name").String()
  380. AuthProxyHeaderProperty = authProxy.Key("header_property").String()
  381. AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true)
  382. authBasic := Cfg.Section("auth.basic")
  383. BasicAuthEnabled = authBasic.Key("enabled").MustBool(true)
  384. // PhantomJS rendering
  385. ImagesDir = filepath.Join(DataPath, "png")
  386. PhantomDir = filepath.Join(HomePath, "vendor/phantomjs")
  387. analytics := Cfg.Section("analytics")
  388. ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
  389. GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String()
  390. GoogleTagManagerId = analytics.Key("google_tag_manager_id").String()
  391. ldapSec := Cfg.Section("auth.ldap")
  392. LdapEnabled = ldapSec.Key("enabled").MustBool(false)
  393. LdapConfigFile = ldapSec.Key("config_file").String()
  394. readSessionConfig()
  395. readSmtpSettings()
  396. readQuotaSettings()
  397. if VerifyEmailEnabled && !Smtp.Enabled {
  398. log.Warn("require_email_validation is enabled but smpt is disabled")
  399. }
  400. return nil
  401. }
  402. func readSessionConfig() {
  403. sec := Cfg.Section("session")
  404. SessionOptions = session.Options{}
  405. SessionOptions.Provider = sec.Key("provider").In("memory", []string{"memory", "file", "redis", "mysql", "postgres", "memcache"})
  406. SessionOptions.ProviderConfig = strings.Trim(sec.Key("provider_config").String(), "\" ")
  407. SessionOptions.CookieName = sec.Key("cookie_name").MustString("grafana_sess")
  408. SessionOptions.CookiePath = AppSubUrl
  409. SessionOptions.Secure = sec.Key("cookie_secure").MustBool()
  410. SessionOptions.Gclifetime = Cfg.Section("session").Key("gc_interval_time").MustInt64(86400)
  411. SessionOptions.Maxlifetime = Cfg.Section("session").Key("session_life_time").MustInt64(86400)
  412. SessionOptions.IDLength = 16
  413. if SessionOptions.Provider == "file" {
  414. SessionOptions.ProviderConfig = makeAbsolute(SessionOptions.ProviderConfig, DataPath)
  415. os.MkdirAll(path.Dir(SessionOptions.ProviderConfig), os.ModePerm)
  416. }
  417. if SessionOptions.CookiePath == "" {
  418. SessionOptions.CookiePath = "/"
  419. }
  420. }
  421. var logLevels = map[string]int{
  422. "Trace": 0,
  423. "Debug": 1,
  424. "Info": 2,
  425. "Warn": 3,
  426. "Error": 4,
  427. "Critical": 5,
  428. }
  429. func initLogging(args *CommandLineArgs) {
  430. //close any existing log handlers.
  431. log.Close()
  432. // Get and check log mode.
  433. LogModes = strings.Split(Cfg.Section("log").Key("mode").MustString("console"), ",")
  434. LogsPath = makeAbsolute(Cfg.Section("paths").Key("logs").String(), HomePath)
  435. LogConfigs = make([]util.DynMap, len(LogModes))
  436. for i, mode := range LogModes {
  437. mode = strings.TrimSpace(mode)
  438. sec, err := Cfg.GetSection("log." + mode)
  439. if err != nil {
  440. log.Fatal(4, "Unknown log mode: %s", mode)
  441. }
  442. // Log level.
  443. levelName := Cfg.Section("log."+mode).Key("level").In("Trace",
  444. []string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"})
  445. level, ok := logLevels[levelName]
  446. if !ok {
  447. log.Fatal(4, "Unknown log level: %s", levelName)
  448. }
  449. // Generate log configuration.
  450. switch mode {
  451. case "console":
  452. formatting := sec.Key("formatting").MustBool(true)
  453. LogConfigs[i] = util.DynMap{
  454. "level": level,
  455. "formatting": formatting,
  456. }
  457. case "file":
  458. logPath := sec.Key("file_name").MustString(filepath.Join(LogsPath, "grafana.log"))
  459. os.MkdirAll(filepath.Dir(logPath), os.ModePerm)
  460. LogConfigs[i] = util.DynMap{
  461. "level": level,
  462. "filename": logPath,
  463. "rotate": sec.Key("log_rotate").MustBool(true),
  464. "maxlines": sec.Key("max_lines").MustInt(1000000),
  465. "maxsize": 1 << uint(sec.Key("max_size_shift").MustInt(28)),
  466. "daily": sec.Key("daily_rotate").MustBool(true),
  467. "maxdays": sec.Key("max_days").MustInt(7),
  468. }
  469. case "conn":
  470. LogConfigs[i] = util.DynMap{
  471. "level": level,
  472. "reconnectOnMsg": sec.Key("reconnect_on_msg").MustBool(),
  473. "reconnect": sec.Key("reconnect").MustBool(),
  474. "net": sec.Key("protocol").In("tcp", []string{"tcp", "unix", "udp"}),
  475. "addr": sec.Key("addr").MustString(":7020"),
  476. }
  477. case "smtp":
  478. LogConfigs[i] = util.DynMap{
  479. "level": level,
  480. "user": sec.Key("user").MustString("example@example.com"),
  481. "passwd": sec.Key("passwd").MustString("******"),
  482. "host": sec.Key("host").MustString("127.0.0.1:25"),
  483. "receivers": sec.Key("receivers").MustString("[]"),
  484. "subject": sec.Key("subject").MustString("Diagnostic message from serve"),
  485. }
  486. case "database":
  487. LogConfigs[i] = util.DynMap{
  488. "level": level,
  489. "driver": sec.Key("driver").String(),
  490. "conn": sec.Key("conn").String(),
  491. }
  492. case "syslog":
  493. LogConfigs[i] = util.DynMap{
  494. "level": level,
  495. "network": sec.Key("network").MustString(""),
  496. "address": sec.Key("address").MustString(""),
  497. "facility": sec.Key("facility").MustString("local7"),
  498. "tag": sec.Key("tag").MustString(""),
  499. }
  500. }
  501. cfgJsonBytes, _ := json.Marshal(LogConfigs[i])
  502. log.NewLogger(Cfg.Section("log").Key("buffer_len").MustInt64(10000), mode, string(cfgJsonBytes))
  503. }
  504. }
  505. func LogConfigurationInfo() {
  506. var text bytes.Buffer
  507. text.WriteString("Configuration Info\n")
  508. text.WriteString("Config files:\n")
  509. for i, file := range configFiles {
  510. text.WriteString(fmt.Sprintf(" [%d]: %s\n", i, file))
  511. }
  512. if len(appliedCommandLineProperties) > 0 {
  513. text.WriteString("Command lines overrides:\n")
  514. for i, prop := range appliedCommandLineProperties {
  515. text.WriteString(fmt.Sprintf(" [%d]: %s\n", i, prop))
  516. }
  517. }
  518. if len(appliedEnvOverrides) > 0 {
  519. text.WriteString("\tEnvironment variables used:\n")
  520. for i, prop := range appliedEnvOverrides {
  521. text.WriteString(fmt.Sprintf(" [%d]: %s\n", i, prop))
  522. }
  523. }
  524. text.WriteString("Paths:\n")
  525. text.WriteString(fmt.Sprintf(" home: %s\n", HomePath))
  526. text.WriteString(fmt.Sprintf(" data: %s\n", DataPath))
  527. text.WriteString(fmt.Sprintf(" logs: %s\n", LogsPath))
  528. text.WriteString(fmt.Sprintf(" plugins: %s\n", PluginsPath))
  529. log.Info(text.String())
  530. }