diff --git a/backend/config.go b/backend/config.go index 91c9f165c..2be1657f4 100644 --- a/backend/config.go +++ b/backend/config.go @@ -2,6 +2,7 @@ package backend import ( "context" + "errors" "fmt" "strconv" "strings" @@ -12,8 +13,13 @@ import ( ) const ( - AppURL = "GF_APP_URL" - ConcurrentQueryCount = "GF_CONCURRENT_QUERY_COUNT" + AppURL = "GF_APP_URL" + ConcurrentQueryCount = "GF_CONCURRENT_QUERY_COUNT" + UserFacingDefaultError = "GF_USER_FACING_DEFAULT_ERROR" + SQLRowLimit = "GF_SQL_ROW_LIMIT" + SQLMaxOpenConnsDefault = "GF_SQL_MAX_OPEN_CONNS_DEFAULT" + SQLMaxIdleConnsDefault = "GF_SQL_MAX_IDLE_CONNS_DEFAULT" + SQLMaxConnLifetimeSecondsDefault = "GF_SQL_MAX_CONN_LIFETIME_SECONDS_DEFAULT" ) type configKey struct{} @@ -151,6 +157,74 @@ func (c *GrafanaCfg) ConcurrentQueryCount() (int, error) { return i, nil } +type SQLConfig struct { + RowLimit int64 + DefaultMaxOpenConns int + DefaultMaxIdleConns int + DefaultMaxConnLifetimeSeconds int +} + +func (c *GrafanaCfg) SQL() (SQLConfig, error) { + // max open connections + maxOpenString, ok := c.config[SQLMaxOpenConnsDefault] + if !ok { + return SQLConfig{}, errors.New("SQLDatasourceMaxOpenConnsDefault not set in config") + } + + maxOpen, err := strconv.Atoi(maxOpenString) + if err != nil { + return SQLConfig{}, errors.New("SQLDatasourceMaxOpenConnsDefault config value is not a valid integer") + } + + // max idle connections + maxIdleString, ok := c.config[SQLMaxIdleConnsDefault] + if !ok { + return SQLConfig{}, errors.New("SQLDatasourceMaxIdleConnsDefault not set in config") + } + + maxIdle, err := strconv.Atoi(maxIdleString) + if err != nil { + return SQLConfig{}, errors.New("SQLDatasourceMaxIdleConnsDefault config value is not a valid integer") + } + + // max connection lifetime + maxLifeString, ok := c.config[SQLMaxConnLifetimeSecondsDefault] + if !ok { + return SQLConfig{}, errors.New("SQLDatasourceMaxConnLifetimeDefault not set in config") + } + + maxLife, err := strconv.Atoi(maxLifeString) + if err != nil { + return SQLConfig{}, errors.New("SQLDatasourceMaxConnLifetimeDefault config value is not a valid integer") + } + + rowLimitString, ok := c.config[SQLRowLimit] + if !ok { + return SQLConfig{}, errors.New("RowLimit not set in config") + } + + rowLimit, err := strconv.ParseInt(rowLimitString, 10, 64) + if err != nil { + return SQLConfig{}, errors.New("RowLimit in config is not a valid integer") + } + + return SQLConfig{ + RowLimit: rowLimit, + DefaultMaxOpenConns: maxOpen, + DefaultMaxIdleConns: maxIdle, + DefaultMaxConnLifetimeSeconds: maxLife, + }, nil +} + +func (c *GrafanaCfg) UserFacingDefaultError() (string, error) { + value, ok := c.config[UserFacingDefaultError] + if !ok { + return "", errors.New("UserFacingDefaultError not set in config") + } + + return value, nil +} + type userAgentKey struct{} // UserAgentFromContext returns user agent from context. diff --git a/backend/config_test.go b/backend/config_test.go index cbcda5c98..6312ab4e2 100644 --- a/backend/config_test.go +++ b/backend/config_test.go @@ -184,3 +184,117 @@ func TestUserAgentFromContext_NoUserAgent(t *testing.T) { require.Equal(t, "0.0.0", result.GrafanaVersion()) require.Equal(t, "Grafana/0.0.0 (unknown; unknown)", result.String()) } + +func TestUserFacingDefaultError(t *testing.T) { + t.Run("it should return the configured default error message", func(t *testing.T) { + cfg := NewGrafanaCfg(map[string]string{ + UserFacingDefaultError: "something failed", + }) + v, err := cfg.UserFacingDefaultError() + require.NoError(t, err) + require.Equal(t, "something failed", v) + }) + + t.Run("it should return an error if the default error message is missing", func(t *testing.T) { + cfg := NewGrafanaCfg(map[string]string{}) + _, err := cfg.UserFacingDefaultError() + require.Error(t, err) + }) +} + +func TestSql(t *testing.T) { + t.Run("it should return the configured sql default values", func(t *testing.T) { + cfg := NewGrafanaCfg(map[string]string{ + SQLRowLimit: "5", + SQLMaxOpenConnsDefault: "11", + SQLMaxIdleConnsDefault: "22", + SQLMaxConnLifetimeSecondsDefault: "33", + }) + v, err := cfg.SQL() + require.NoError(t, err) + require.Equal(t, int64(5), v.RowLimit) + require.Equal(t, 11, v.DefaultMaxOpenConns) + require.Equal(t, 22, v.DefaultMaxIdleConns) + require.Equal(t, 33, v.DefaultMaxConnLifetimeSeconds) + }) + + t.Run("it should return an error if any of the defaults is missing", func(t *testing.T) { + cfg1 := NewGrafanaCfg(map[string]string{ + SQLMaxOpenConnsDefault: "11", + SQLMaxIdleConnsDefault: "22", + SQLMaxConnLifetimeSecondsDefault: "33", + }) + + cfg2 := NewGrafanaCfg(map[string]string{ + SQLRowLimit: "5", + SQLMaxIdleConnsDefault: "22", + SQLMaxConnLifetimeSecondsDefault: "33", + }) + + cfg3 := NewGrafanaCfg(map[string]string{ + SQLRowLimit: "5", + SQLMaxOpenConnsDefault: "11", + SQLMaxConnLifetimeSecondsDefault: "33", + }) + + cfg4 := NewGrafanaCfg(map[string]string{ + SQLRowLimit: "5", + SQLMaxOpenConnsDefault: "11", + SQLMaxIdleConnsDefault: "22", + }) + + _, err := cfg1.SQL() + require.ErrorContains(t, err, "not set") + + _, err = cfg2.SQL() + require.ErrorContains(t, err, "not set") + + _, err = cfg3.SQL() + require.ErrorContains(t, err, "not set") + + _, err = cfg4.SQL() + require.ErrorContains(t, err, "not set") + }) + + t.Run("it should return an error if any of the defaults is not an integer", func(t *testing.T) { + cfg1 := NewGrafanaCfg(map[string]string{ + SQLRowLimit: "not-an-integer", + SQLMaxOpenConnsDefault: "11", + SQLMaxIdleConnsDefault: "22", + SQLMaxConnLifetimeSecondsDefault: "33", + }) + + cfg2 := NewGrafanaCfg(map[string]string{ + SQLRowLimit: "5", + SQLMaxOpenConnsDefault: "11", + SQLMaxIdleConnsDefault: "not-an-integer", + SQLMaxConnLifetimeSecondsDefault: "33", + }) + + cfg3 := NewGrafanaCfg(map[string]string{ + SQLRowLimit: "5", + SQLMaxOpenConnsDefault: "11", + SQLMaxIdleConnsDefault: "not-an-integer", + SQLMaxConnLifetimeSecondsDefault: "33", + }) + + cfg4 := NewGrafanaCfg(map[string]string{ + SQLRowLimit: "5", + SQLMaxOpenConnsDefault: "11", + SQLMaxIdleConnsDefault: "22", + SQLMaxConnLifetimeSecondsDefault: "not-an-integer", + }) + + _, err := cfg1.SQL() + require.ErrorContains(t, err, "not a valid integer") + + _, err = cfg2.SQL() + require.ErrorContains(t, err, "not a valid integer") + + _, err = cfg3.SQL() + require.ErrorContains(t, err, "not a valid integer") + + _, err = cfg4.SQL() + require.ErrorContains(t, err, "not a valid integer") + }) +}