2014-10-05 09:50:04 -05:00
// Copyright 2014 Unknwon
// Copyright 2014 Torkel Ödegaard
2014-10-04 06:33:20 -05:00
package setting
import (
2015-04-09 05:16:59 -05:00
"bytes"
2022-09-26 15:25:34 -05:00
"crypto/tls"
2021-07-21 10:54:05 -05:00
"encoding/json"
2020-10-13 05:30:09 -05:00
"errors"
2015-02-12 06:31:41 -06:00
"fmt"
2022-09-26 15:25:34 -05:00
"io/fs"
2019-02-01 04:47:21 -06:00
"net/http"
2014-10-04 06:33:20 -05:00
"net/url"
"os"
"path"
"path/filepath"
2021-11-30 04:28:52 -06:00
"regexp"
2021-03-12 07:30:21 -06:00
"strconv"
2014-10-04 06:33:20 -05:00
"strings"
2018-07-02 06:33:39 -05:00
"time"
2023-01-30 02:26:42 -06:00
"github.com/gobwas/glob"
2023-02-27 03:23:38 -06:00
"github.com/prometheus/common/model"
"gopkg.in/ini.v1"
2021-03-12 07:30:21 -06:00
"github.com/grafana/grafana-aws-sdk/pkg/awsds"
2024-03-19 09:56:40 -05:00
"github.com/grafana/grafana-azure-sdk-go/v2/azsettings"
2021-09-21 06:08:52 -05:00
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
2021-11-24 13:56:07 -06:00
2024-06-12 23:11:35 -05:00
"github.com/grafana/grafana/pkg/apimachinery/identity"
2024-08-30 04:59:42 -05:00
"github.com/grafana/grafana/pkg/apiserver/rest"
2019-05-13 01:45:54 -05:00
"github.com/grafana/grafana/pkg/infra/log"
2015-04-19 02:14:50 -05:00
"github.com/grafana/grafana/pkg/util"
2024-02-05 09:25:54 -06:00
"github.com/grafana/grafana/pkg/util/osutil"
2014-10-04 06:33:20 -05:00
)
type Scheme string
const (
2020-10-02 08:45:45 -05:00
HTTPScheme Scheme = "http"
HTTPSScheme Scheme = "https"
HTTP2Scheme Scheme = "h2"
SocketScheme Scheme = "socket"
2014-10-04 06:33:20 -05:00
)
2014-12-16 05:04:08 -06:00
const (
2021-05-10 10:03:10 -05:00
RedactedPassword = "*********"
2020-10-02 08:45:45 -05:00
DefaultHTTPAddr = "0.0.0.0"
Dev = "development"
Prod = "production"
2021-08-25 08:11:22 -05:00
ApplicationName = "Grafana"
2014-12-16 05:04:08 -06:00
)
2021-03-19 04:14:49 -05:00
// zoneInfo names environment variable for setting the path to look for the timezone database in go
const zoneInfo = "ZONEINFO"
2014-10-04 06:33:20 -05:00
var (
2024-01-23 05:36:22 -06:00
customInitPath = "conf/custom.ini"
2014-10-04 06:33:20 -05:00
// App settings.
2024-01-23 05:36:22 -06:00
Env = Dev
AppUrl string
AppSubUrl string
2014-10-04 06:33:20 -05:00
2015-01-05 03:46:58 -06:00
// build
2023-09-22 06:17:10 -05:00
BuildVersion string
BuildCommit string
EnterpriseBuildCommit string
BuildBranch string
BuildStamp int64
IsEnterprise bool
2015-01-05 03:46:58 -06:00
2018-11-15 07:42:09 -06:00
// packaging
Packaging = "unknown"
2020-12-11 04:44:44 -06:00
CookieSecure bool
CookieSameSiteDisabled bool
CookieSameSiteMode http . SameSite
2014-10-04 06:33:20 -05:00
)
2018-10-12 00:55:36 -05:00
// TODO move all global vars to this struct
2018-04-30 09:21:04 -05:00
type Cfg struct {
2023-03-06 13:06:52 -06:00
Target [ ] string
2019-04-22 10:58:24 -05:00
Raw * ini . File
Logger log . Logger
2018-04-30 09:21:04 -05:00
2024-01-23 05:36:22 -06:00
// for logging purposes
configFiles [ ] string
appliedCommandLineProperties [ ] string
appliedEnvOverrides [ ] string
2018-10-12 00:55:36 -05:00
// HTTP Server Settings
2024-03-22 10:13:22 -05:00
CertFile string
KeyFile string
2024-08-06 17:18:32 -05:00
CertPassword string
2024-03-22 10:13:22 -05:00
CertWatchInterval time . Duration
HTTPAddr string
HTTPPort string
Env string
AppURL string
AppSubURL string
InstanceName string
ServeFromSubPath bool
StaticRootPath string
Protocol Scheme
SocketGid int
SocketMode int
SocketPath string
RouterLogging bool
Domain string
CDNRootURL * url . URL
ReadTimeout time . Duration
EnableGzip bool
EnforceDomain bool
MinTLSVersion string
2018-10-12 00:55:36 -05:00
2021-08-25 08:11:22 -05:00
// Security settings
2021-11-10 04:52:16 -06:00
SecretKey string
2021-08-25 08:11:22 -05:00
EmailCodeValidMinutes int
2020-06-11 09:14:05 -05:00
// build
2023-09-22 06:17:10 -05:00
BuildVersion string
BuildCommit string
EnterpriseBuildCommit string
BuildBranch string
BuildStamp int64
IsEnterprise bool
2020-06-11 09:14:05 -05:00
// packaging
Packaging string
2018-05-01 08:51:15 -05:00
// Paths
2022-09-28 01:29:35 -05:00
HomePath string
ProvisioningPath string
DataPath string
LogsPath string
PluginsPath string
BundledPluginsPath string
EnterpriseLicensePath string
2018-05-01 08:51:15 -05:00
2018-04-30 09:21:04 -05:00
// SMTP email settings
Smtp SmtpSettings
2018-05-24 08:26:27 -05:00
// Rendering
2020-04-27 10:25:08 -05:00
ImagesDir string
2021-05-12 10:16:57 -05:00
CSVsDir string
2024-02-08 06:09:34 -06:00
PDFsDir string
2020-04-27 10:25:08 -05:00
RendererUrl string
RendererCallbackUrl string
2022-08-30 05:09:38 -05:00
RendererAuthToken string
2020-04-27 10:25:08 -05:00
RendererConcurrentRequestLimit int
2022-11-03 06:06:55 -05:00
RendererRenderKeyLifeTime time . Duration
2024-02-26 06:27:34 -06:00
RendererDefaultImageWidth int
RendererDefaultImageHeight int
RendererDefaultImageScale float64
2018-09-24 08:58:22 -05:00
2019-02-05 14:09:55 -06:00
// Security
2020-12-11 04:44:44 -06:00
DisableInitAdminCreation bool
DisableBruteForceLoginProtection bool
CookieSecure bool
CookieSameSiteDisabled bool
CookieSameSiteMode http . SameSite
AllowEmbedding bool
XSSProtectionHeader bool
ContentTypeProtectionHeader bool
StrictTransportSecurity bool
StrictTransportSecurityMaxAge int
StrictTransportSecurityPreload bool
StrictTransportSecuritySubDomains bool
2021-01-12 00:42:32 -06:00
// CSPEnabled toggles Content Security Policy support.
CSPEnabled bool
// CSPTemplate contains the Content Security Policy template.
2022-11-16 11:11:26 -06:00
CSPTemplate string
// CSPReportEnabled toggles Content Security Policy Report Only support.
CSPReportOnlyEnabled bool
// CSPReportOnlyTemplate contains the Content Security Policy Report Only template.
2024-10-17 09:56:50 -05:00
CSPReportOnlyTemplate string
AngularSupportEnabled bool
EnableFrontendSandboxForPlugins [ ] string
DisableGravatar bool
DataProxyWhiteList map [ string ] bool
ActionsAllowPostURL string
2019-02-05 14:09:55 -06:00
2022-09-28 01:29:35 -05:00
TempDataLifetime time . Duration
// Plugins
2021-05-27 05:45:06 -05:00
PluginsEnableAlpha bool
PluginsAppsSkipVerifyTLS bool
PluginSettings PluginSettings
PluginsAllowUnsigned [ ] string
PluginCatalogURL string
2021-11-15 03:53:35 -06:00
PluginCatalogHiddenPlugins [ ] string
2021-05-27 05:45:06 -05:00
PluginAdminEnabled bool
PluginAdminExternalManageEnabled bool
2023-04-28 09:00:48 -05:00
PluginForcePublicKeyDownload bool
2023-06-16 04:20:30 -05:00
PluginSkipPublicKeyDownload bool
2023-09-14 05:58:12 -05:00
DisablePlugins [ ] string
2023-12-12 03:20:21 -06:00
HideAngularDeprecation [ ] string
2023-10-24 09:21:37 -05:00
PluginInstallToken string
2023-12-13 03:25:17 -06:00
ForwardHostEnvVars [ ] string
2024-08-21 09:11:55 -05:00
PreinstallPlugins [ ] InstallPlugin
PreinstallPluginsAsync bool
2022-09-28 01:29:35 -05:00
2023-02-07 05:49:16 -06:00
PluginsCDNURLTemplate string
PluginLogBackendRequests bool
2023-01-27 08:08:17 -06:00
2022-09-28 01:29:35 -05:00
// Panels
DisableSanitizeHtml bool
2020-10-19 09:58:16 -05:00
// Metrics
2018-11-01 06:07:11 -05:00
MetricsEndpointEnabled bool
2018-11-14 14:42:47 -06:00
MetricsEndpointBasicAuthUsername string
MetricsEndpointBasicAuthPassword string
2019-09-17 02:32:24 -05:00
MetricsEndpointDisableTotalStats bool
2023-08-16 08:05:19 -05:00
// MetricsIncludeTeamLabel configures grafana to set a label for
// the team responsible for the code at Grafana labs. We don't expect anyone else to
// use this setting.
MetricsIncludeTeamLabel bool
2023-08-03 03:01:44 -05:00
MetricsTotalStatsIntervalSeconds int
2020-10-19 09:58:16 -05:00
MetricsGrafanaEnvironmentInfo map [ string ] string
2019-01-22 07:06:44 -06:00
2020-06-22 11:00:39 -05:00
// Dashboards
2024-01-23 05:36:22 -06:00
DashboardVersionsToKeep int
MinRefreshInterval string
2020-06-22 11:00:39 -05:00
DefaultHomeDashboardPath string
2019-02-05 14:09:55 -06:00
// Auth
2024-02-16 04:58:05 -06:00
LoginCookieName string
LoginMaxInactiveLifetime time . Duration
LoginMaxLifetime time . Duration
TokenRotationIntervalMinutes int
SigV4AuthEnabled bool
SigV4VerboseLogging bool
AzureAuthEnabled bool
AzureSkipOrgRoleSync bool
BasicAuthEnabled bool
BasicAuthStrongPasswordPolicy bool
AdminUser string
AdminPassword string
DisableLogin bool
AdminEmail string
DisableLoginForm bool
SignoutRedirectUrl string
IDResponseHeaderEnabled bool
IDResponseHeaderPrefix string
IDResponseHeaderNamespaces map [ string ] struct { }
2024-09-27 02:11:59 -05:00
ManagedServiceAccountsEnabled bool
2020-12-11 04:44:44 -06:00
2021-02-24 11:08:13 -06:00
// AWS Plugin Auth
2024-02-05 12:59:32 -06:00
AWSAllowedAuthProviders [ ] string
AWSAssumeRoleEnabled bool
AWSSessionDuration string
AWSExternalId string
AWSListMetricsPageLimit int
AWSForwardSettingsPlugins [ ] string
2021-02-24 11:08:13 -06:00
2021-05-12 09:23:37 -05:00
// Azure Cloud settings
2022-04-01 06:26:49 -05:00
Azure * azsettings . AzureSettings
2021-05-12 09:23:37 -05:00
2020-12-11 04:44:44 -06:00
// Auth proxy settings
2024-03-01 04:31:06 -06:00
AuthProxy AuthProxySettings
2019-02-22 05:11:26 -06:00
2020-03-30 09:44:58 -05:00
// OAuth
2024-08-19 11:57:37 -05:00
OAuthAutoLogin bool
2024-09-27 05:11:27 -05:00
OAuthLoginErrorMessage string
2024-08-19 11:57:37 -05:00
OAuthCookieMaxAge int
OAuthAllowInsecureEmailLookup bool
OAuthRefreshTokenServerLockMinWaitMs int64
2020-03-30 09:44:58 -05:00
2024-04-02 10:45:15 -05:00
JWTAuth AuthJWTSettings
ExtJWTAuth ExtJWTSettings
2023-05-11 08:12:53 -05:00
2024-11-14 07:50:55 -06:00
PasswordlessMagicLinkAuth AuthPasswordlessMagicLinkSettings
2024-01-12 02:24:16 -06:00
// SSO Settings Auth
2024-01-23 08:48:06 -06:00
SSOSettingsReloadInterval time . Duration
SSOSettingsConfigurableProviders map [ string ] bool
2024-01-12 02:24:16 -06:00
2019-03-14 07:04:47 -05:00
// Dataproxy
2021-07-15 07:30:06 -05:00
SendUserHeader bool
DataProxyLogging bool
DataProxyTimeout int
DataProxyDialTimeout int
DataProxyTLSHandshakeTimeout int
DataProxyExpectContinueTimeout int
DataProxyMaxConnsPerHost int
DataProxyMaxIdleConns int
DataProxyKeepAlive int
DataProxyIdleConnTimeout int
2021-09-10 08:51:06 -05:00
ResponseLimit int64
2021-09-13 08:27:51 -05:00
DataProxyRowLimit int64
2023-02-28 07:10:05 -06:00
DataProxyUserAgent string
2019-03-14 07:04:47 -05:00
2019-03-03 14:48:00 -06:00
// DistributedCache
2024-11-04 10:35:31 -06:00
RemoteCacheOptions * RemoteCacheSettings
2019-03-12 01:32:47 -05:00
2023-03-16 04:54:01 -05:00
ViewersCanEdit bool
2019-03-12 01:32:47 -05:00
EditorsCanAdmin bool
2019-06-26 01:47:03 -05:00
ApiKeyMaxSecondsToLive int64
2019-09-09 01:58:57 -05:00
2022-01-26 11:44:20 -06:00
// Check if a feature toggle is enabled
2023-11-13 13:39:01 -06:00
// Deprecated: use featuremgmt.FeatureFlags
2022-01-26 11:44:20 -06:00
IsFeatureToggleEnabled func ( key string ) bool // filled in dynamically
2020-12-11 04:44:44 -06:00
AnonymousEnabled bool
AnonymousOrgName string
AnonymousOrgRole string
2020-06-17 00:39:50 -05:00
AnonymousHideVersion bool
2023-12-12 04:57:25 -06:00
AnonymousDeviceLimit int64
2020-09-02 01:07:31 -05:00
2020-09-07 09:19:33 -05:00
DateFormats DateFormats
2020-10-13 05:30:09 -05:00
// User
2024-02-16 11:54:59 -06:00
UserInviteMaxLifetime time . Duration
HiddenUsers map [ string ] struct { }
CaseInsensitiveLogin bool // Login and Email will be considered case insensitive
2024-06-24 09:54:56 -05:00
UserLastSeenUpdateInterval time . Duration
2024-02-16 11:54:59 -06:00
VerificationEmailMaxLifetime time . Duration
2020-10-13 05:30:09 -05:00
2022-11-22 03:08:40 -06:00
// Service Accounts
SATokenExpirationDayLimit int
2020-09-02 01:07:31 -05:00
// Annotations
2021-03-08 07:16:07 -06:00
AnnotationCleanupJobBatchSize int64
2022-09-23 05:04:41 -05:00
AnnotationMaximumTagsLength int64
2020-09-02 01:07:31 -05:00
AlertingAnnotationCleanupSetting AnnotationCleanupSettings
DashboardAnnotationCleanupSettings AnnotationCleanupSettings
APIAnnotationCleanupSettings AnnotationCleanupSettings
2020-11-12 05:29:43 -06:00
2022-06-28 02:25:30 -05:00
// GrafanaJavascriptAgent config
GrafanaJavascriptAgent GrafanaJavascriptAgent
2020-12-28 05:24:42 -06:00
// Data sources
DataSourceLimit int
2024-01-29 04:47:28 -06:00
// Number of queries to be executed concurrently. Only for the datasource supports concurrency.
ConcurrentQueryCount int
2020-12-28 05:24:42 -06:00
2024-01-31 11:09:24 -06:00
// IP range access control
IPRangeACEnabled bool
2024-02-23 10:13:21 -06:00
IPRangeACAllowedURLs [ ] * url . URL
2024-01-31 11:09:24 -06:00
IPRangeACSecretKey string
2023-04-17 03:44:05 -05:00
// SQL Data sources
SqlDatasourceMaxOpenConnsDefault int
SqlDatasourceMaxIdleConnsDefault int
SqlDatasourceMaxConnLifetimeDefault int
2020-12-11 04:44:44 -06:00
// Snapshots
2024-02-02 00:40:11 -06:00
SnapshotEnabled bool
ExternalSnapshotUrl string
ExternalSnapshotName string
ExternalEnabled bool
2023-01-26 07:28:11 -06:00
2024-02-02 00:40:11 -06:00
// Only used in https://snapshots.raintank.io/
2020-12-11 04:44:44 -06:00
SnapshotPublicMode bool
ErrTemplateName string
2023-09-21 07:45:43 -05:00
StackID string
Slug string
2024-03-01 03:39:50 -06:00
LocalFileSystemAvailable bool
2021-03-08 00:02:49 -06:00
// Analytics
2022-04-06 03:50:21 -05:00
CheckForGrafanaUpdates bool
CheckForPluginUpdates bool
2021-09-15 06:00:38 -05:00
ReportingDistributor string
ReportingEnabled bool
ApplicationInsightsConnectionString string
ApplicationInsightsEndpointUrl string
2022-04-14 08:18:03 -05:00
FeedbackLinksEnabled bool
2024-07-08 09:37:45 -05:00
ReportingStaticContext map [ string ] string
2021-03-08 00:02:49 -06:00
2023-02-21 04:19:07 -06:00
// Frontend analytics
GoogleAnalyticsID string
GoogleAnalytics4ID string
GoogleAnalytics4SendManualPageViews bool
GoogleTagManagerID string
RudderstackDataPlaneURL string
RudderstackWriteKey string
RudderstackSDKURL string
RudderstackConfigURL string
2023-10-23 07:43:33 -05:00
RudderstackIntegrationsURL string
2023-03-03 08:39:53 -06:00
IntercomSecret string
2024-10-29 13:20:54 -05:00
FrontendAnalyticsConsoleReporting bool
2023-02-21 04:19:07 -06:00
2020-12-11 04:44:44 -06:00
// LDAP
2023-03-22 12:41:59 -05:00
LDAPAuthEnabled bool
2023-02-10 12:01:55 -06:00
LDAPSkipOrgRoleSync bool
LDAPConfigFilePath string
LDAPAllowSignup bool
LDAPActiveSyncEnabled bool
LDAPSyncCron string
2020-12-11 04:44:44 -06:00
2022-11-22 06:18:34 -06:00
DefaultTheme string
DefaultLanguage string
HomePage string
2021-01-07 04:36:13 -06:00
2022-11-14 13:08:10 -06:00
Quota QuotaSettings
2024-01-23 05:36:22 -06:00
// User settings
AllowUserSignUp bool
AllowUserOrgCreate bool
VerifyEmailEnabled bool
LoginHint string
PasswordHint string
DisableSignoutMenu bool
ExternalUserMngLinkUrl string
ExternalUserMngLinkName string
ExternalUserMngInfo string
2022-02-21 10:34:47 -06:00
AutoAssignOrg bool
AutoAssignOrgId int
AutoAssignOrgRole string
2024-03-12 03:35:13 -05:00
LoginDefaultOrgId int64
2022-02-21 10:34:47 -06:00
OAuthSkipOrgRoleUpdateSync bool
2021-01-22 11:27:33 -06:00
// ExpressionsEnabled specifies whether expressions are enabled.
ExpressionsEnabled bool
2021-03-10 05:41:29 -06:00
ImageUploadProvider string
2021-05-27 14:03:18 -05:00
// LiveMaxConnections is a maximum number of WebSocket connections to
// Grafana Live ws endpoint (per Grafana server instance). 0 disables
// Live, -1 means unlimited connections.
LiveMaxConnections int
2021-06-24 03:07:09 -05:00
// LiveHAEngine is a type of engine to use to achieve HA with Grafana Live.
// Zero value means in-memory single node setup.
LiveHAEngine string
2024-09-25 15:20:35 -05:00
// LiveHAPRefix is a prefix for HA engine keys.
LiveHAPrefix string
2021-06-24 03:07:09 -05:00
// LiveHAEngineAddress is a connection address for Live HA engine.
2023-10-11 03:45:24 -05:00
LiveHAEngineAddress string
LiveHAEnginePassword string
2021-07-01 01:30:09 -05:00
// LiveAllowedOrigins is a set of origins accepted by Live. If not provided
// then Live uses AppURL as the only allowed origin.
LiveAllowedOrigins [ ] string
2021-06-08 09:08:25 -05:00
Add a separate grafana.com API URL setting (#59506)
The GrafanaComURL setting is currently used in two places:
- the /api/gnet endpoint, which proxies all requests to the URL
configured in GrafanaComURL
- OAuth logins using grafana.com, where the auth URL, token URL and
redirect URL are all configured to use the GrafanaComURL.
This has worked fine until now because almost all Grafana instances have
just used the default value, https://grafana.com. However, we now have a
few different grafana.com's, some of which are behind IAP. The IAP
causes the /api/gnet proxy to fail because the required cookies are not
present in the request (how could they be?). Setting the
[grafana_net.url] setting to an internal-only URL improves the situation
slightly - the proxy works again just fine - but breaks any OAuth logins
using grafana.com, because the user must be redirected to a publicly
accessible URL.
This commit adds an additional setting, `[grafana_com.api_url]`,
which can be used to tell Grafana to use the new API URL when proxying
requests to the grafana.com API, while still using the existing
`GrafanaComURL` setting for other things.
The setting will fall back to the GrafanaComURL setting + "/api" if unset.
2022-12-01 11:06:12 -06:00
// Grafana.com URL, used for OAuth redirect.
2021-06-08 09:08:25 -05:00
GrafanaComURL string
Add a separate grafana.com API URL setting (#59506)
The GrafanaComURL setting is currently used in two places:
- the /api/gnet endpoint, which proxies all requests to the URL
configured in GrafanaComURL
- OAuth logins using grafana.com, where the auth URL, token URL and
redirect URL are all configured to use the GrafanaComURL.
This has worked fine until now because almost all Grafana instances have
just used the default value, https://grafana.com. However, we now have a
few different grafana.com's, some of which are behind IAP. The IAP
causes the /api/gnet proxy to fail because the required cookies are not
present in the request (how could they be?). Setting the
[grafana_net.url] setting to an internal-only URL improves the situation
slightly - the proxy works again just fine - but breaks any OAuth logins
using grafana.com, because the user must be redirected to a publicly
accessible URL.
This commit adds an additional setting, `[grafana_com.api_url]`,
which can be used to tell Grafana to use the new API URL when proxying
requests to the grafana.com API, while still using the existing
`GrafanaComURL` setting for other things.
The setting will fall back to the GrafanaComURL setting + "/api" if unset.
2022-12-01 11:06:12 -06:00
// Grafana.com API URL. Can be set separately to GrafanaComURL
// in case API is not publicly accessible.
// Defaults to GrafanaComURL setting + "/api" if unset.
GrafanaComAPIURL string
2023-07-21 09:22:28 -05:00
2024-05-30 08:52:16 -05:00
// Grafana.com SSO API token used for Unified SSO between instances and Grafana.com.
GrafanaComSSOAPIToken string
2021-07-21 15:48:20 -05:00
// Geomap base layer config
2023-08-30 10:46:47 -05:00
GeomapDefaultBaseLayerConfig map [ string ] any
2021-07-21 15:48:20 -05:00
GeomapEnableCustomBaseLayers bool
2021-08-13 07:14:36 -05:00
// Unified Alerting
2021-09-20 02:12:21 -05:00
UnifiedAlerting UnifiedAlertingSettings
2022-01-28 10:55:09 -06:00
// Query history
QueryHistoryEnabled bool
2022-04-24 16:55:10 -05:00
2022-07-25 12:11:17 -05:00
Storage StorageSettings
2022-09-20 18:09:55 -05:00
Search SearchSettings
2022-11-29 23:50:59 -06:00
SecureSocksDSProxy SecureSocksDSProxySettings
2023-02-08 14:11:46 -06:00
// SAML Auth
2023-08-03 11:02:40 -05:00
SAMLAuthEnabled bool
SAMLSkipOrgRoleSync bool
SAMLRoleValuesGrafanaAdmin string
2023-02-08 14:11:46 -06:00
2023-05-25 08:38:30 -05:00
// OAuth2 Server
OAuth2ServerEnabled bool
// OAuth2Server supports the two recommended key types from the RFC https://www.rfc-editor.org/rfc/rfc7518#section-3.1: RS256 and ES256
OAuth2ServerGeneratedKeyTypeForClient string
OAuth2ServerAccessTokenLifespan time . Duration
2024-07-24 11:31:26 -05:00
RBAC RBACSettings
2023-04-14 10:17:59 -05:00
2024-06-18 03:04:18 -05:00
Zanzana ZanzanaSettings
2022-09-26 15:25:34 -05:00
// GRPC Server.
2024-04-30 09:18:03 -05:00
GRPCServerNetwork string
GRPCServerAddress string
GRPCServerTLSConfig * tls . Config
GRPCServerEnableLogging bool // log request and response of each unary gRPC call
GRPCServerMaxRecvMsgSize int
GRPCServerMaxSendMsgSize int
2022-11-30 11:12:34 -06:00
CustomResponseHeaders map [ string ] string
2023-04-28 08:19:06 -05:00
2023-06-16 10:46:47 -05:00
// This is used to override the general error message shown to users when we want to obfuscate a sensitive backend error
UserFacingDefaultError string
2023-04-28 08:19:06 -05:00
// DatabaseInstrumentQueries is used to decide if database queries
// should be instrumented with metrics, logs and traces.
// This needs to be on the global object since its used in the
// sqlstore package and HTTP middlewares.
DatabaseInstrumentQueries bool
2023-07-24 15:12:59 -05:00
2023-12-19 04:43:54 -06:00
// Public dashboards
PublicDashboardsEnabled bool
2024-03-11 07:29:44 -05:00
// Cloud Migration
2024-03-25 10:43:28 -05:00
CloudMigration CloudMigrationSettings
2024-03-11 07:29:44 -05:00
2023-07-24 15:12:59 -05:00
// Feature Management Settings
FeatureManagement FeatureMgmtSettings
2024-01-23 05:36:22 -06:00
// Alerting
AlertingEvaluationTimeout time . Duration
AlertingNotificationTimeout time . Duration
AlertingMaxAttempts int
AlertingMinInterval int64
// Explore UI
2024-07-17 11:47:49 -05:00
ExploreEnabled bool
ExploreDefaultTimeOffset string
2024-01-23 05:36:22 -06:00
// Help UI
HelpEnabled bool
// Profile UI
ProfileEnabled bool
// News Feed
NewsFeedEnabled bool
2024-03-05 09:41:19 -06:00
// Experimental scope settings
ScopesListScopesURL string
ScopesListDashboardsURL string
2024-04-22 07:39:24 -05:00
//Short Links
ShortLinkExpiration int
2024-08-30 04:59:42 -05:00
// Unified Storage
2024-10-29 13:24:31 -05:00
UnifiedStorage map [ string ] UnifiedStorageConfig
IndexPath string
IndexWorkers int
IndexMaxBatchSize int
IndexListLimit int
2024-08-30 04:59:42 -05:00
}
type UnifiedStorageConfig struct {
2024-09-24 14:03:15 -05:00
DualWriterMode rest . DualWriterMode
DualWriterPeriodicDataSyncJobEnabled bool
2018-04-30 09:21:04 -05:00
}
2024-08-13 09:57:55 -05:00
type InstallPlugin struct {
2024-08-14 10:04:59 -05:00
ID string ` json:"id" `
Version string ` json:"version" `
2024-08-13 09:57:55 -05:00
}
2023-02-27 08:28:49 -06:00
// AddChangePasswordLink returns if login form is disabled or not since
// the same intention can be used to hide both features.
func ( cfg * Cfg ) AddChangePasswordLink ( ) bool {
2024-01-04 03:46:55 -06:00
return ! ( cfg . DisableLoginForm || cfg . DisableLogin )
2023-02-27 08:28:49 -06:00
}
2015-04-08 07:10:04 -05:00
type CommandLineArgs struct {
2015-04-12 02:15:49 -05:00
Config string
HomePath string
Args [ ] string
2015-04-08 07:10:04 -05:00
}
2023-06-07 01:57:41 -05:00
func ( cfg * Cfg ) parseAppUrlAndSubUrl ( section * ini . Section ) ( string , string , error ) {
2020-09-08 04:33:04 -05:00
appUrl := valueAsString ( section , "root_url" , "http://localhost:3000/" )
2015-01-27 03:09:54 -06:00
if appUrl [ len ( appUrl ) - 1 ] != '/' {
appUrl += "/"
}
// Check if has app suburl.
2015-01-30 07:21:32 -06:00
url , err := url . Parse ( appUrl )
2015-01-27 03:09:54 -06:00
if err != nil {
2021-11-08 10:56:56 -06:00
cfg . Logger . Error ( "Invalid root_url." , "url" , appUrl , "error" , err )
2021-10-26 10:36:24 -05:00
os . Exit ( 1 )
2015-01-27 03:09:54 -06:00
}
2020-09-08 04:33:04 -05:00
appSubUrl := strings . TrimSuffix ( url . Path , "/" )
2019-04-25 01:29:07 -05:00
return appUrl , appSubUrl , nil
2015-01-27 03:09:54 -06:00
}
2015-02-06 07:17:40 -06:00
func ToAbsUrl ( relativeUrl string ) string {
2015-02-04 04:35:59 -06:00
return AppUrl + relativeUrl
}
2021-05-10 10:03:10 -05:00
func RedactedValue ( key , value string ) string {
2023-05-10 05:30:50 -05:00
if value == "" {
return ""
}
2021-05-10 10:03:10 -05:00
uppercased := strings . ToUpper ( key )
// Sensitive information: password, secrets etc
for _ , pattern := range [ ] string {
"PASSWORD" ,
"SECRET" ,
"PROVIDER_CONFIG" ,
"PRIVATE_KEY" ,
"SECRET_KEY" ,
"CERTIFICATE" ,
2021-08-19 04:41:26 -05:00
"ACCOUNT_KEY" ,
2021-11-30 04:28:52 -06:00
"ENCRYPTION_KEY" ,
"VAULT_TOKEN" ,
2024-01-19 13:39:09 -06:00
"CLIENT_SECRET" ,
2024-02-01 11:37:36 -06:00
"ENTERPRISE_LICENSE" ,
2024-05-01 13:15:44 -05:00
"GF_ENTITY_API_DB_PASS" ,
2021-05-10 10:03:10 -05:00
} {
2021-11-30 04:28:52 -06:00
if match , err := regexp . MatchString ( pattern , uppercased ) ; match && err == nil {
2021-05-10 10:03:10 -05:00
return RedactedPassword
}
}
2021-11-30 04:28:52 -06:00
for _ , exception := range [ ] string {
"RUDDERSTACK" ,
"APPLICATION_INSIGHTS" ,
"SENTRY" ,
2021-05-10 10:03:10 -05:00
} {
2021-11-30 04:28:52 -06:00
if strings . Contains ( uppercased , exception ) {
return value
2021-05-10 10:03:10 -05:00
}
}
2021-11-30 04:28:52 -06:00
if u , err := RedactedURL ( value ) ; err == nil {
return u
}
2021-05-10 10:03:10 -05:00
return value
2016-06-28 11:37:59 -05:00
}
2021-11-30 04:28:52 -06:00
func RedactedURL ( value string ) ( string , error ) {
// Value could be a list of URLs
chunks := util . SplitString ( value )
for i , chunk := range chunks {
var hasTmpPrefix bool
const tmpPrefix = "http://"
if ! strings . Contains ( chunk , "://" ) {
chunk = tmpPrefix + chunk
hasTmpPrefix = true
}
u , err := url . Parse ( chunk )
if err != nil {
return "" , err
}
redacted := u . Redacted ( )
if hasTmpPrefix {
redacted = strings . Replace ( redacted , tmpPrefix , "" , 1 )
}
chunks [ i ] = redacted
}
if strings . Contains ( value , "," ) {
return strings . Join ( chunks , "," ) , nil
}
return strings . Join ( chunks , " " ) , nil
}
2024-01-23 05:36:22 -06:00
func ( cfg * Cfg ) applyEnvVariableOverrides ( file * ini . File ) error {
cfg . appliedEnvOverrides = make ( [ ] string , 0 )
2018-04-30 09:21:04 -05:00
for _ , section := range file . Sections ( ) {
2015-02-12 06:31:41 -06:00
for _ , key := range section . Keys ( ) {
2021-11-30 04:28:52 -06:00
envKey := EnvKey ( section . Name ( ) , key . Name ( ) )
2015-02-12 06:31:41 -06:00
envValue := os . Getenv ( envKey )
if len ( envValue ) > 0 {
key . SetValue ( envValue )
2024-01-23 05:36:22 -06:00
cfg . appliedEnvOverrides = append ( cfg . appliedEnvOverrides , fmt . Sprintf ( "%s=%s" , envKey , RedactedValue ( envKey , envValue ) ) )
2015-02-12 06:31:41 -06:00
}
}
}
2018-03-28 11:03:33 -05:00
return nil
2015-02-12 04:55:55 -06:00
}
2020-10-19 09:58:16 -05:00
func ( cfg * Cfg ) readGrafanaEnvironmentMetrics ( ) error {
environmentMetricsSection := cfg . Raw . Section ( "metrics.environment_info" )
keys := environmentMetricsSection . Keys ( )
cfg . MetricsGrafanaEnvironmentInfo = make ( map [ string ] string , len ( keys ) )
2023-12-22 05:02:52 -06:00
cfg . MetricsGrafanaEnvironmentInfo [ "version" ] = cfg . BuildVersion
cfg . MetricsGrafanaEnvironmentInfo [ "commit" ] = cfg . BuildCommit
if cfg . EnterpriseBuildCommit != "NA" && cfg . EnterpriseBuildCommit != "" {
cfg . MetricsGrafanaEnvironmentInfo [ "enterprise_commit" ] = cfg . EnterpriseBuildCommit
}
2020-10-19 09:58:16 -05:00
for _ , key := range keys {
labelName := model . LabelName ( key . Name ( ) )
labelValue := model . LabelValue ( key . Value ( ) )
if ! labelName . IsValid ( ) {
return fmt . Errorf ( "invalid label name in [metrics.environment_info] configuration. name %q" , labelName )
}
if ! labelValue . IsValid ( ) {
return fmt . Errorf ( "invalid label value in [metrics.environment_info] configuration. name %q value %q" , labelName , labelValue )
}
cfg . MetricsGrafanaEnvironmentInfo [ string ( labelName ) ] = string ( labelValue )
}
return nil
}
2022-09-23 05:04:41 -05:00
func ( cfg * Cfg ) readAnnotationSettings ( ) error {
2021-03-08 07:16:07 -06:00
section := cfg . Raw . Section ( "annotations" )
cfg . AnnotationCleanupJobBatchSize = section . Key ( "cleanupjob_batchsize" ) . MustInt64 ( 100 )
2022-09-23 05:04:41 -05:00
cfg . AnnotationMaximumTagsLength = section . Key ( "tags_length" ) . MustInt64 ( 500 )
switch {
case cfg . AnnotationMaximumTagsLength > 4096 :
// ensure that the configuration does not exceed the respective column size
return fmt . Errorf ( "[annotations.tags_length] configuration exceeds the maximum allowed (4096)" )
case cfg . AnnotationMaximumTagsLength > 500 :
cfg . Logger . Info ( "[annotations.tags_length] has been increased from its default value; this may affect the performance" , "tagLength" , cfg . AnnotationMaximumTagsLength )
case cfg . AnnotationMaximumTagsLength < 500 :
cfg . Logger . Warn ( "[annotations.tags_length] is too low; the minimum allowed (500) is enforced" )
cfg . AnnotationMaximumTagsLength = 500
}
2021-03-08 07:16:07 -06:00
2020-09-02 01:07:31 -05:00
dashboardAnnotation := cfg . Raw . Section ( "annotations.dashboard" )
apiIAnnotation := cfg . Raw . Section ( "annotations.api" )
var newAnnotationCleanupSettings = func ( section * ini . Section , maxAgeField string ) AnnotationCleanupSettings {
2020-11-02 12:26:19 -06:00
maxAge , err := gtime . ParseDuration ( section . Key ( maxAgeField ) . MustString ( "" ) )
2020-09-02 01:07:31 -05:00
if err != nil {
maxAge = 0
}
return AnnotationCleanupSettings {
MaxAge : maxAge ,
MaxCount : section . Key ( "max_annotations_to_keep" ) . MustInt64 ( 0 ) ,
}
}
2024-02-26 16:04:27 -06:00
alertingAnnotations := cfg . Raw . Section ( "unified_alerting.state_history.annotations" )
if alertingAnnotations . Key ( "max_age" ) . Value ( ) == "" && section . Key ( "max_annotations_to_keep" ) . Value ( ) == "" {
2024-03-07 15:01:11 -06:00
// Although this section is not documented anymore, we decided to keep it to avoid potential data-loss when user upgrades Grafana and does not change the setting.
// TODO delete some time after Grafana 11.
2024-02-26 16:04:27 -06:00
alertingSection := cfg . Raw . Section ( "alerting" )
cleanup := newAnnotationCleanupSettings ( alertingSection , "max_annotation_age" )
if cleanup . MaxCount > 0 || cleanup . MaxAge > 0 {
cfg . Logger . Warn ( "settings 'max_annotations_to_keep' and 'max_annotation_age' in section [alerting] are deprecated. Please use settings 'max_annotations_to_keep' and 'max_age' in section [unified_alerting.state_history.annotations]" )
}
cfg . AlertingAnnotationCleanupSetting = cleanup
} else {
cfg . AlertingAnnotationCleanupSetting = newAnnotationCleanupSettings ( alertingAnnotations , "max_age" )
}
2020-09-02 01:07:31 -05:00
cfg . DashboardAnnotationCleanupSettings = newAnnotationCleanupSettings ( dashboardAnnotation , "max_age" )
cfg . APIAnnotationCleanupSettings = newAnnotationCleanupSettings ( apiIAnnotation , "max_age" )
2022-09-23 05:04:41 -05:00
return nil
2020-09-02 01:07:31 -05:00
}
2021-01-22 11:27:33 -06:00
func ( cfg * Cfg ) readExpressionsSettings ( ) {
expressions := cfg . Raw . Section ( "expressions" )
cfg . ExpressionsEnabled = expressions . Key ( "enabled" ) . MustBool ( true )
}
2020-09-02 01:07:31 -05:00
type AnnotationCleanupSettings struct {
MaxAge time . Duration
MaxCount int64
}
2021-11-30 04:28:52 -06:00
func EnvKey ( sectionName string , keyName string ) string {
2020-09-22 09:22:19 -05:00
sN := strings . ToUpper ( strings . ReplaceAll ( sectionName , "." , "_" ) )
sN = strings . ReplaceAll ( sN , "-" , "_" )
kN := strings . ToUpper ( strings . ReplaceAll ( keyName , "." , "_" ) )
2020-01-10 08:33:54 -06:00
envKey := fmt . Sprintf ( "GF_%s_%s" , sN , kN )
return envKey
}
2024-01-23 05:36:22 -06:00
func ( cfg * Cfg ) applyCommandLineDefaultProperties ( props map [ string ] string , file * ini . File ) {
cfg . appliedCommandLineProperties = make ( [ ] string , 0 )
2018-04-30 09:21:04 -05:00
for _ , section := range file . Sections ( ) {
2015-04-09 05:16:59 -05:00
for _ , key := range section . Keys ( ) {
keyString := fmt . Sprintf ( "default.%s.%s" , section . Name ( ) , key . Name ( ) )
value , exists := props [ keyString ]
if exists {
key . SetValue ( value )
2024-01-23 05:36:22 -06:00
cfg . appliedCommandLineProperties = append ( cfg . appliedCommandLineProperties ,
2021-05-10 10:03:10 -05:00
fmt . Sprintf ( "%s=%s" , keyString , RedactedValue ( keyString , value ) ) )
2015-04-09 05:16:59 -05:00
}
2015-04-08 13:31:42 -05:00
}
}
}
2024-01-23 05:36:22 -06:00
func ( cfg * Cfg ) applyCommandLineProperties ( props map [ string ] string , file * ini . File ) {
2018-04-30 09:21:04 -05:00
for _ , section := range file . Sections ( ) {
2017-10-01 13:02:25 -05:00
sectionName := section . Name ( ) + "."
2019-09-30 08:16:04 -05:00
if section . Name ( ) == ini . DefaultSection {
2017-10-01 13:02:25 -05:00
sectionName = ""
}
2015-04-09 05:16:59 -05:00
for _ , key := range section . Keys ( ) {
2017-10-01 13:02:25 -05:00
keyString := sectionName + key . Name ( )
2015-04-09 05:16:59 -05:00
value , exists := props [ keyString ]
if exists {
2024-01-23 05:36:22 -06:00
cfg . appliedCommandLineProperties = append ( cfg . appliedCommandLineProperties , fmt . Sprintf ( "%s=%s" , keyString , value ) )
2017-10-01 13:02:25 -05:00
key . SetValue ( value )
2015-04-09 05:16:59 -05:00
}
}
}
}
2015-02-15 15:57:49 -06:00
2023-06-07 01:57:41 -05:00
func ( cfg * Cfg ) getCommandLineProperties ( args [ ] string ) map [ string ] string {
2015-04-09 05:16:59 -05:00
props := make ( map [ string ] string )
2014-10-04 06:33:20 -05:00
2015-04-09 05:16:59 -05:00
for _ , arg := range args {
if ! strings . HasPrefix ( arg , "cfg:" ) {
continue
}
trimmed := strings . TrimPrefix ( arg , "cfg:" )
parts := strings . Split ( trimmed , "=" )
if len ( parts ) != 2 {
2021-11-08 10:56:56 -06:00
cfg . Logger . Error ( "Invalid command line argument." , "argument" , arg )
2021-10-26 10:36:24 -05:00
os . Exit ( 1 )
2015-01-27 03:09:54 -06:00
}
2015-01-05 00:59:18 -06:00
2015-04-09 05:16:59 -05:00
props [ parts [ 0 ] ] = parts [ 1 ]
}
return props
}
func makeAbsolute ( path string , root string ) string {
if filepath . IsAbs ( path ) {
return path
}
return filepath . Join ( root , path )
}
2021-08-25 08:11:22 -05:00
func ( cfg * Cfg ) loadSpecifiedConfigFile ( configFile string , masterFile * ini . File ) error {
2015-04-12 02:15:49 -05:00
if configFile == "" {
2024-01-23 05:36:22 -06:00
configFile = filepath . Join ( cfg . HomePath , customInitPath )
2015-04-12 02:15:49 -05:00
// return without error if custom file does not exist
if ! pathExists ( configFile ) {
2016-06-30 18:37:06 -05:00
return nil
2015-04-12 02:15:49 -05:00
}
}
2015-04-10 03:58:32 -05:00
userConfig , err := ini . Load ( configFile )
if err != nil {
2020-11-05 06:07:06 -06:00
return fmt . Errorf ( "failed to parse %q: %w" , configFile , err )
2015-04-10 03:58:32 -05:00
}
2024-02-05 09:25:54 -06:00
// micro-optimization since we don't need to share this ini file. In
// general, prefer to leave this flag as true as it is by default to prevent
// data races
2016-11-28 10:55:18 -06:00
userConfig . BlockMode = false
2015-04-10 03:58:32 -05:00
for _ , section := range userConfig . Sections ( ) {
for _ , key := range section . Keys ( ) {
if key . Value ( ) == "" {
continue
}
2018-04-30 09:21:04 -05:00
defaultSec , err := masterFile . GetSection ( section . Name ( ) )
2015-04-10 03:58:32 -05:00
if err != nil {
2018-04-30 09:21:04 -05:00
defaultSec , _ = masterFile . NewSection ( section . Name ( ) )
2015-04-10 03:58:32 -05:00
}
defaultKey , err := defaultSec . GetKey ( key . Name ( ) )
if err != nil {
2015-11-19 09:50:17 -06:00
defaultKey , _ = defaultSec . NewKey ( key . Name ( ) , key . Value ( ) )
2015-04-10 03:58:32 -05:00
}
defaultKey . SetValue ( key . Value ( ) )
}
}
2024-01-23 05:36:22 -06:00
cfg . configFiles = append ( cfg . configFiles , configFile )
2016-06-30 18:37:06 -05:00
return nil
2015-04-10 03:58:32 -05:00
}
2021-08-25 08:11:22 -05:00
func ( cfg * Cfg ) loadConfiguration ( args CommandLineArgs ) ( * ini . File , error ) {
2015-04-09 05:16:59 -05:00
// load config defaults
2024-01-23 05:36:22 -06:00
defaultConfigFile := path . Join ( cfg . HomePath , "conf/defaults.ini" )
cfg . configFiles = append ( cfg . configFiles , defaultConfigFile )
2015-04-09 05:16:59 -05:00
2016-12-06 00:36:10 -06:00
// check if config file exists
if _ , err := os . Stat ( defaultConfigFile ) ; os . IsNotExist ( err ) {
fmt . Println ( "Grafana-server Init Failed: Could not find config defaults, make sure homepath command line parameter is set or working directory is homepath" )
os . Exit ( 1 )
}
// load defaults
2018-04-30 09:21:04 -05:00
parsedFile , err := ini . Load ( defaultConfigFile )
2015-04-09 05:16:59 -05:00
if err != nil {
2020-03-23 07:37:53 -05:00
fmt . Printf ( "Failed to parse defaults.ini, %v\n" , err )
2016-12-06 00:36:10 -06:00
os . Exit ( 1 )
2018-04-30 09:21:04 -05:00
return nil , err
2015-04-09 05:16:59 -05:00
}
// command line props
2021-11-08 10:56:56 -06:00
commandLineProps := cfg . getCommandLineProperties ( args . Args )
2015-04-09 05:16:59 -05:00
// load default overrides
2024-01-23 05:36:22 -06:00
cfg . applyCommandLineDefaultProperties ( commandLineProps , parsedFile )
2015-04-09 05:16:59 -05:00
// load specified config file
2021-08-25 08:11:22 -05:00
err = cfg . loadSpecifiedConfigFile ( args . Config , parsedFile )
2016-06-30 18:37:06 -05:00
if err != nil {
2019-06-04 15:00:05 -05:00
err2 := cfg . initLogging ( parsedFile )
if err2 != nil {
return nil , err2
2019-04-25 01:29:07 -05:00
}
2021-11-08 10:56:56 -06:00
cfg . Logger . Error ( err . Error ( ) )
2021-10-26 10:36:24 -05:00
os . Exit ( 1 )
2016-06-30 18:37:06 -05:00
}
2014-10-04 06:33:20 -05:00
2015-04-09 05:16:59 -05:00
// apply environment overrides
2024-01-23 05:36:22 -06:00
err = cfg . applyEnvVariableOverrides ( parsedFile )
2018-03-28 11:03:33 -05:00
if err != nil {
2018-04-30 09:21:04 -05:00
return nil , err
2018-03-28 11:03:33 -05:00
}
2015-04-09 05:16:59 -05:00
// apply command line overrides
2024-01-23 05:36:22 -06:00
cfg . applyCommandLineProperties ( commandLineProps , parsedFile )
2015-04-08 07:10:04 -05:00
2015-04-09 05:16:59 -05:00
// evaluate config values containing environment variables
2020-06-10 07:58:42 -05:00
err = expandConfig ( parsedFile )
if err != nil {
return nil , err
}
2015-05-14 03:15:46 -05:00
// update data path and logging config
2020-09-08 04:33:04 -05:00
dataPath := valueAsString ( parsedFile . Section ( "paths" ) , "data" , "" )
2024-01-23 05:36:22 -06:00
cfg . DataPath = makeAbsolute ( dataPath , cfg . HomePath )
2019-04-25 01:29:07 -05:00
err = cfg . initLogging ( parsedFile )
if err != nil {
return nil , err
}
2018-03-28 11:03:33 -05:00
2022-02-03 09:20:02 -06:00
cfg . Logger . Info ( fmt . Sprintf ( "Starting %s" , ApplicationName ) , "version" , BuildVersion , "commit" , BuildCommit , "branch" , BuildBranch , "compiled" , time . Unix ( BuildStamp , 0 ) )
2018-04-30 09:21:04 -05:00
return parsedFile , err
2015-04-09 05:16:59 -05:00
}
2015-04-12 02:15:49 -05:00
func pathExists ( path string ) bool {
_ , err := os . Stat ( path )
if err == nil {
return true
}
if os . IsNotExist ( err ) {
return false
}
return false
}
2021-08-25 08:11:22 -05:00
func ( cfg * Cfg ) setHomePath ( args CommandLineArgs ) {
2015-04-12 02:15:49 -05:00
if args . HomePath != "" {
2021-08-25 08:11:22 -05:00
cfg . HomePath = args . HomePath
2015-04-12 02:15:49 -05:00
return
}
2021-01-12 00:42:32 -06:00
var err error
2021-08-25 08:11:22 -05:00
cfg . HomePath , err = filepath . Abs ( "." )
2021-01-12 00:42:32 -06:00
if err != nil {
panic ( err )
}
2021-08-25 08:11:22 -05:00
2015-04-12 02:15:49 -05:00
// check if homepath is correct
2021-08-25 08:11:22 -05:00
if pathExists ( filepath . Join ( cfg . HomePath , "conf/defaults.ini" ) ) {
2015-04-12 02:15:49 -05:00
return
}
// try down one path
2021-08-25 08:11:22 -05:00
if pathExists ( filepath . Join ( cfg . HomePath , "../conf/defaults.ini" ) ) {
cfg . HomePath = filepath . Join ( cfg . HomePath , "../" )
2015-04-12 02:15:49 -05:00
}
}
2018-04-27 15:14:36 -05:00
var skipStaticRootValidation = false
2015-09-11 01:58:45 -05:00
2019-04-22 10:58:24 -05:00
func NewCfg ( ) * Cfg {
return & Cfg {
2024-01-23 05:36:22 -06:00
Env : Dev ,
2023-09-05 05:04:39 -05:00
Target : [ ] string { "all" } ,
Logger : log . New ( "settings" ) ,
Raw : ini . Empty ( ) ,
Azure : & azsettings . AzureSettings { } ,
2023-11-13 09:55:15 -06:00
// Avoid nil pointer
IsFeatureToggleEnabled : func ( _ string ) bool {
return false
} ,
2019-04-22 10:58:24 -05:00
}
}
2023-11-13 09:55:15 -06:00
// Deprecated: Avoid using IsFeatureToggleEnabled from settings. If you need to access
// feature flags, read them from the FeatureToggle (or FeatureManager) interface
func NewCfgWithFeatures ( features func ( string ) bool ) * Cfg {
cfg := NewCfg ( )
cfg . IsFeatureToggleEnabled = features
return cfg
}
2021-08-25 08:11:22 -05:00
func NewCfgFromArgs ( args CommandLineArgs ) ( * Cfg , error ) {
cfg := NewCfg ( )
if err := cfg . Load ( args ) ; err != nil {
return nil , err
}
return cfg , nil
}
2024-01-23 05:36:22 -06:00
// NewCfgFromBytes specialized function to create a new Cfg from bytes (INI file).
func NewCfgFromBytes ( bytes [ ] byte ) ( * Cfg , error ) {
parsedFile , err := ini . Load ( bytes )
if err != nil {
return nil , fmt . Errorf ( "failed to parse bytes as INI file: %w" , err )
}
return NewCfgFromINIFile ( parsedFile )
}
// NewCfgFromINIFile specialized function to create a new Cfg from an ini.File.
func NewCfgFromINIFile ( iniFile * ini . File ) ( * Cfg , error ) {
cfg := NewCfg ( )
if err := cfg . parseINIFile ( iniFile ) ; err != nil {
return nil , fmt . Errorf ( "failed to parse setting from INI file: %w" , err )
}
return cfg , nil
}
2019-04-22 10:58:24 -05:00
func ( cfg * Cfg ) validateStaticRootPath ( ) error {
2015-09-11 01:58:45 -05:00
if skipStaticRootValidation {
return nil
2015-09-10 06:34:32 -05:00
}
2024-01-23 05:36:22 -06:00
if _ , err := os . Stat ( path . Join ( cfg . StaticRootPath , "build" ) ) ; err != nil {
2019-04-22 10:58:24 -05:00
cfg . Logger . Error ( "Failed to detect generated javascript files in public/build" )
2015-09-10 06:34:32 -05:00
}
2017-10-01 13:02:25 -05:00
return nil
2015-09-10 06:34:32 -05:00
}
2021-08-25 08:11:22 -05:00
func ( cfg * Cfg ) Load ( args CommandLineArgs ) error {
cfg . setHomePath ( args )
2018-04-30 09:21:04 -05:00
2024-06-18 07:32:19 -05:00
// Fix for missing IANA db on Windows or Alpine
2021-03-19 04:14:49 -05:00
_ , zoneInfoSet := os . LookupEnv ( zoneInfo )
2024-06-18 07:32:19 -05:00
if ! zoneInfoSet {
2024-01-23 05:36:22 -06:00
if err := os . Setenv ( zoneInfo , filepath . Join ( cfg . HomePath , "tools" , "zoneinfo.zip" ) ) ; err != nil {
2021-03-19 04:14:49 -05:00
cfg . Logger . Error ( "Can't set ZONEINFO environment variable" , "err" , err )
}
}
2018-10-12 00:55:36 -05:00
iniFile , err := cfg . loadConfiguration ( args )
2018-03-28 11:03:33 -05:00
if err != nil {
return err
}
2015-04-09 05:16:59 -05:00
2024-01-23 05:36:22 -06:00
err = cfg . parseINIFile ( iniFile )
if err != nil {
return err
}
2018-04-30 09:21:04 -05:00
2024-01-23 05:36:22 -06:00
cfg . LogConfigSources ( )
return nil
}
// nolint:gocyclo
func ( cfg * Cfg ) parseINIFile ( iniFile * ini . File ) error {
cfg . Raw = iniFile
2018-04-30 09:21:04 -05:00
2020-06-11 09:14:05 -05:00
cfg . BuildVersion = BuildVersion
cfg . BuildCommit = BuildCommit
2023-09-22 06:17:10 -05:00
cfg . EnterpriseBuildCommit = EnterpriseBuildCommit
2020-06-11 09:14:05 -05:00
cfg . BuildStamp = BuildStamp
cfg . BuildBranch = BuildBranch
cfg . IsEnterprise = IsEnterprise
cfg . Packaging = Packaging
2020-12-15 12:09:04 -06:00
cfg . ErrTemplateName = "error"
2020-12-11 04:44:44 -06:00
2023-08-31 08:12:01 -05:00
Target := valueAsString ( iniFile . Section ( "" ) , "target" , "all" )
2023-08-03 08:19:01 -05:00
if Target != "" {
2023-11-15 14:26:28 -06:00
cfg . Target = util . SplitString ( Target )
2023-08-03 08:19:01 -05:00
}
2024-01-23 05:36:22 -06:00
cfg . Env = valueAsString ( iniFile . Section ( "" ) , "app_mode" , "development" )
2023-09-21 07:45:43 -05:00
cfg . StackID = valueAsString ( iniFile . Section ( "environment" ) , "stack_id" , "" )
2023-09-21 07:59:49 -05:00
cfg . Slug = valueAsString ( iniFile . Section ( "environment" ) , "stack_slug" , "" )
2024-03-01 03:39:50 -06:00
cfg . LocalFileSystemAvailable = iniFile . Section ( "environment" ) . Key ( "local_file_system_available" ) . MustBool ( true )
2024-01-23 05:36:22 -06:00
cfg . InstanceName = valueAsString ( iniFile . Section ( "" ) , "instance_name" , "unknown_instance_name" )
2020-09-08 04:33:04 -05:00
plugins := valueAsString ( iniFile . Section ( "paths" ) , "plugins" , "" )
2024-01-23 05:36:22 -06:00
cfg . PluginsPath = makeAbsolute ( plugins , cfg . HomePath )
cfg . BundledPluginsPath = makeAbsolute ( "plugins-bundled" , cfg . HomePath )
2020-09-08 04:33:04 -05:00
provisioning := valueAsString ( iniFile . Section ( "paths" ) , "provisioning" , "" )
2024-01-23 05:36:22 -06:00
cfg . ProvisioningPath = makeAbsolute ( provisioning , cfg . HomePath )
2020-09-08 04:33:04 -05:00
2021-02-01 03:13:09 -06:00
if err := cfg . readServerSettings ( iniFile ) ; err != nil {
2020-08-26 06:11:57 -05:00
return err
}
2021-07-15 07:30:06 -05:00
if err := readDataProxySettings ( iniFile , cfg ) ; err != nil {
return err
2021-07-07 05:13:53 -05:00
}
2020-08-26 06:11:57 -05:00
if err := readSecuritySettings ( iniFile , cfg ) ; err != nil {
return err
}
2020-12-11 04:44:44 -06:00
if err := readSnapshotsSettings ( cfg , iniFile ) ; err != nil {
2020-08-26 06:11:57 -05:00
return err
}
2022-09-26 15:25:34 -05:00
if err := readGRPCServerSettings ( cfg , iniFile ) ; err != nil {
return err
}
2020-08-26 06:11:57 -05:00
// read dashboard settings
dashboards := iniFile . Section ( "dashboards" )
2024-01-23 05:36:22 -06:00
cfg . DashboardVersionsToKeep = dashboards . Key ( "versions_to_keep" ) . MustInt ( 20 )
cfg . MinRefreshInterval = valueAsString ( dashboards , "min_refresh_interval" , "5s" )
2020-08-26 06:11:57 -05:00
cfg . DefaultHomeDashboardPath = dashboards . Key ( "default_home_dashboard_path" ) . MustString ( "" )
2014-10-04 06:33:20 -05:00
2020-08-26 06:11:57 -05:00
if err := readUserSettings ( iniFile , cfg ) ; err != nil {
return err
}
2022-11-22 03:08:40 -06:00
if err := readServiceAccountSettings ( iniFile , cfg ) ; err != nil {
return err
}
2020-08-26 06:11:57 -05:00
if err := readAuthSettings ( iniFile , cfg ) ; err != nil {
return err
}
2023-05-25 08:38:30 -05:00
readOAuth2ServerSettings ( cfg )
2024-07-24 11:31:26 -05:00
cfg . readRBACSettings ( )
2024-06-18 03:04:18 -05:00
cfg . readZanzanaSettings ( )
2021-08-25 08:11:22 -05:00
if err := cfg . readRenderingSettings ( iniFile ) ; err != nil {
2020-08-26 06:11:57 -05:00
return err
}
cfg . TempDataLifetime = iniFile . Section ( "paths" ) . Key ( "temp_data_lifetime" ) . MustDuration ( time . Second * 3600 * 24 )
cfg . MetricsEndpointEnabled = iniFile . Section ( "metrics" ) . Key ( "enabled" ) . MustBool ( true )
2020-09-08 04:33:04 -05:00
cfg . MetricsEndpointBasicAuthUsername = valueAsString ( iniFile . Section ( "metrics" ) , "basic_auth_username" , "" )
cfg . MetricsEndpointBasicAuthPassword = valueAsString ( iniFile . Section ( "metrics" ) , "basic_auth_password" , "" )
2020-08-26 06:11:57 -05:00
cfg . MetricsEndpointDisableTotalStats = iniFile . Section ( "metrics" ) . Key ( "disable_total_stats" ) . MustBool ( false )
2023-08-16 08:05:19 -05:00
cfg . MetricsIncludeTeamLabel = iniFile . Section ( "metrics" ) . Key ( "include_team_label" ) . MustBool ( false )
2023-08-03 03:01:44 -05:00
cfg . MetricsTotalStatsIntervalSeconds = iniFile . Section ( "metrics" ) . Key ( "total_stats_collector_interval_seconds" ) . MustInt ( 1800 )
2020-08-26 06:11:57 -05:00
analytics := iniFile . Section ( "analytics" )
2022-04-06 03:50:21 -05:00
cfg . CheckForGrafanaUpdates = analytics . Key ( "check_for_updates" ) . MustBool ( true )
cfg . CheckForPluginUpdates = analytics . Key ( "check_for_plugin_updates" ) . MustBool ( true )
2023-02-21 04:19:07 -06:00
cfg . GoogleAnalyticsID = analytics . Key ( "google_analytics_ua_id" ) . String ( )
cfg . GoogleAnalytics4ID = analytics . Key ( "google_analytics_4_id" ) . String ( )
cfg . GoogleAnalytics4SendManualPageViews = analytics . Key ( "google_analytics_4_send_manual_page_views" ) . MustBool ( false )
cfg . GoogleTagManagerID = analytics . Key ( "google_tag_manager_id" ) . String ( )
cfg . RudderstackWriteKey = analytics . Key ( "rudderstack_write_key" ) . String ( )
cfg . RudderstackDataPlaneURL = analytics . Key ( "rudderstack_data_plane_url" ) . String ( )
cfg . RudderstackSDKURL = analytics . Key ( "rudderstack_sdk_url" ) . String ( )
cfg . RudderstackConfigURL = analytics . Key ( "rudderstack_config_url" ) . String ( )
2023-10-23 07:43:33 -05:00
cfg . RudderstackIntegrationsURL = analytics . Key ( "rudderstack_integrations_url" ) . String ( )
2023-03-03 08:39:53 -06:00
cfg . IntercomSecret = analytics . Key ( "intercom_secret" ) . String ( )
2024-10-29 13:20:54 -05:00
cfg . FrontendAnalyticsConsoleReporting = analytics . Key ( "browser_console_reporter" ) . MustBool ( false )
2022-04-14 08:18:03 -05:00
2021-03-10 03:14:00 -06:00
cfg . ReportingEnabled = analytics . Key ( "reporting_enabled" ) . MustBool ( true )
cfg . ReportingDistributor = analytics . Key ( "reporting_distributor" ) . MustString ( "grafana-labs" )
2022-04-14 08:18:03 -05:00
2021-03-10 03:14:00 -06:00
if len ( cfg . ReportingDistributor ) >= 100 {
cfg . ReportingDistributor = cfg . ReportingDistributor [ : 100 ]
2021-02-10 03:07:32 -06:00
}
2022-04-14 08:18:03 -05:00
2021-09-15 06:00:38 -05:00
cfg . ApplicationInsightsConnectionString = analytics . Key ( "application_insights_connection_string" ) . String ( )
cfg . ApplicationInsightsEndpointUrl = analytics . Key ( "application_insights_endpoint_url" ) . String ( )
2022-04-14 08:18:03 -05:00
cfg . FeedbackLinksEnabled = analytics . Key ( "feedback_links_enabled" ) . MustBool ( true )
2020-08-26 06:11:57 -05:00
2024-07-08 09:37:45 -05:00
// parse reporting static context string of key=value, key=value pairs into an object
cfg . ReportingStaticContext = make ( map [ string ] string )
for _ , pair := range strings . Split ( analytics . Key ( "reporting_static_context" ) . String ( ) , "," ) {
kv := strings . Split ( pair , "=" )
if len ( kv ) == 2 {
cfg . ReportingStaticContext [ strings . TrimSpace ( "_static_context_" + kv [ 0 ] ) ] = strings . TrimSpace ( kv [ 1 ] )
}
}
2024-01-23 05:36:22 -06:00
if err := cfg . readAlertingSettings ( iniFile ) ; err != nil {
2020-08-26 06:11:57 -05:00
return err
2019-08-16 10:06:54 -05:00
}
2021-08-13 07:14:36 -05:00
2020-08-26 06:11:57 -05:00
explore := iniFile . Section ( "explore" )
2024-01-23 05:36:22 -06:00
cfg . ExploreEnabled = explore . Key ( "enabled" ) . MustBool ( true )
2020-08-26 06:11:57 -05:00
2024-07-17 11:47:49 -05:00
exploreDefaultTimeOffset := valueAsString ( explore , "defaultTimeOffset" , "1h" )
// we want to ensure the value parses as a duration, but we send it forward as a string to the frontend
if _ , err := gtime . ParseDuration ( exploreDefaultTimeOffset ) ; err != nil {
return err
} else {
cfg . ExploreDefaultTimeOffset = exploreDefaultTimeOffset
}
2022-03-29 10:27:53 -05:00
help := iniFile . Section ( "help" )
2024-01-23 05:36:22 -06:00
cfg . HelpEnabled = help . Key ( "enabled" ) . MustBool ( true )
2022-03-29 10:27:53 -05:00
profile := iniFile . Section ( "profile" )
2024-01-23 05:36:22 -06:00
cfg . ProfileEnabled = profile . Key ( "enabled" ) . MustBool ( true )
2022-03-29 10:27:53 -05:00
2023-06-01 07:35:05 -05:00
news := iniFile . Section ( "news" )
2024-01-23 05:36:22 -06:00
cfg . NewsFeedEnabled = news . Key ( "news_feed_enabled" ) . MustBool ( true )
2023-06-01 07:35:05 -05:00
2022-01-28 10:55:09 -06:00
queryHistory := iniFile . Section ( "query_history" )
2022-06-21 07:57:44 -05:00
cfg . QueryHistoryEnabled = queryHistory . Key ( "enabled" ) . MustBool ( true )
2022-01-28 10:55:09 -06:00
2024-04-22 07:39:24 -05:00
shortLinks := iniFile . Section ( "short_links" )
cfg . ShortLinkExpiration = shortLinks . Key ( "expire_time" ) . MustInt ( 7 )
if cfg . ShortLinkExpiration > 365 {
cfg . Logger . Warn ( "short_links expire_time must be less than 366 days. Setting to 365 days" )
cfg . ShortLinkExpiration = 365
}
2020-08-26 06:11:57 -05:00
panelsSection := iniFile . Section ( "panels" )
cfg . DisableSanitizeHtml = panelsSection . Key ( "disable_sanitize_html" ) . MustBool ( false )
2021-11-15 03:53:35 -06:00
if err := cfg . readPluginSettings ( iniFile ) ; err != nil {
return err
}
2015-01-27 03:09:54 -06:00
2023-11-13 13:39:01 -06:00
// nolint:staticcheck
2021-09-29 09:16:40 -05:00
if err := cfg . readFeatureToggles ( iniFile ) ; err != nil {
return err
}
if err := cfg . ReadUnifiedAlertingSettings ( iniFile ) ; err != nil {
return err
2020-08-26 06:11:57 -05:00
}
// check old location for this option
if panelsSection . Key ( "enable_alpha" ) . MustBool ( false ) {
cfg . PluginsEnableAlpha = true
}
2023-02-08 14:11:46 -06:00
cfg . readSAMLConfig ( )
2020-08-26 06:11:57 -05:00
cfg . readLDAPConfig ( )
2021-03-12 07:30:21 -06:00
cfg . handleAWSConfig ( )
2021-05-12 09:23:37 -05:00
cfg . readAzureSettings ( )
2024-02-09 09:35:58 -06:00
cfg . readAuthJWTSettings ( )
2024-04-02 10:45:15 -05:00
cfg . readAuthExtJWTSettings ( )
2024-03-01 04:31:06 -06:00
cfg . readAuthProxySettings ( )
2020-08-26 06:11:57 -05:00
cfg . readSessionConfig ( )
2024-11-14 07:50:55 -06:00
cfg . readPasswordlessMagicLinkSettings ( )
2023-12-14 12:59:43 -06:00
if err := cfg . readSmtpSettings ( ) ; err != nil {
return err
}
2022-09-23 05:04:41 -05:00
if err := cfg . readAnnotationSettings ( ) ; err != nil {
return err
}
2022-11-14 13:08:10 -06:00
cfg . readQuotaSettings ( )
2021-01-22 11:27:33 -06:00
cfg . readExpressionsSettings ( )
2020-10-19 09:58:16 -05:00
if err := cfg . readGrafanaEnvironmentMetrics ( ) ; err != nil {
return err
}
2020-08-26 06:11:57 -05:00
2020-12-28 05:24:42 -06:00
cfg . readDataSourcesSettings ( )
2024-01-31 11:09:24 -06:00
cfg . readDataSourceSecuritySettings ( )
2023-04-17 03:44:05 -05:00
cfg . readSqlDataSourceSettings ( )
2020-12-28 05:24:42 -06:00
2022-07-25 12:11:17 -05:00
cfg . Storage = readStorageSettings ( iniFile )
2022-09-20 18:09:55 -05:00
cfg . Search = readSearchSettings ( iniFile )
2022-04-24 16:55:10 -05:00
2024-01-23 05:36:22 -06:00
var err error
2022-11-29 23:50:59 -06:00
cfg . SecureSocksDSProxy , err = readSecureSocksDSProxySettings ( iniFile )
if err != nil {
// if the proxy is misconfigured, disable it rather than crashing
cfg . SecureSocksDSProxy . Enabled = false
cfg . Logger . Error ( "secure_socks_datasource_proxy unable to start up" , "err" , err . Error ( ) )
}
2024-01-23 05:36:22 -06:00
if cfg . VerifyEmailEnabled && ! cfg . Smtp . Enabled {
2021-11-08 10:56:56 -06:00
cfg . Logger . Warn ( "require_email_validation is enabled but smtp is disabled" )
2020-08-26 06:11:57 -05:00
}
2023-01-18 06:59:50 -06:00
// check old key name
2024-01-23 05:36:22 -06:00
grafanaComUrl := valueAsString ( iniFile . Section ( "grafana_net" ) , "url" , "" )
if grafanaComUrl == "" {
grafanaComUrl = valueAsString ( iniFile . Section ( "grafana_com" ) , "url" , "https://grafana.com" )
2020-08-26 06:11:57 -05:00
}
2024-01-23 05:36:22 -06:00
cfg . GrafanaComURL = grafanaComUrl
2020-08-26 06:11:57 -05:00
2024-01-23 05:36:22 -06:00
cfg . GrafanaComAPIURL = valueAsString ( iniFile . Section ( "grafana_com" ) , "api_url" , grafanaComUrl + "/api" )
2024-05-30 08:52:16 -05:00
cfg . GrafanaComSSOAPIToken = valueAsString ( iniFile . Section ( "grafana_com" ) , "sso_api_token" , "" )
2020-08-26 06:11:57 -05:00
imageUploadingSection := iniFile . Section ( "external_image_storage" )
2021-03-10 05:41:29 -06:00
cfg . ImageUploadProvider = valueAsString ( imageUploadingSection , "provider" , "" )
2017-01-16 05:43:59 -06:00
2020-08-26 06:11:57 -05:00
enterprise := iniFile . Section ( "enterprise" )
2020-09-08 04:33:04 -05:00
cfg . EnterpriseLicensePath = valueAsString ( enterprise , "license_path" , filepath . Join ( cfg . DataPath , "license.jwt" ) )
2015-09-11 01:58:45 -05:00
2021-07-21 10:54:05 -05:00
geomapSection := iniFile . Section ( "geomap" )
2021-07-21 15:48:20 -05:00
basemapJSON := valueAsString ( geomapSection , "default_baselayer_config" , "" )
2021-07-21 10:54:05 -05:00
if basemapJSON != "" {
2023-08-30 10:46:47 -05:00
layer := make ( map [ string ] any )
2024-01-23 05:36:22 -06:00
err := json . Unmarshal ( [ ] byte ( basemapJSON ) , & layer )
2021-07-21 10:54:05 -05:00
if err != nil {
2021-07-21 15:48:20 -05:00
cfg . Logger . Error ( "Error reading json from default_baselayer_config" , "error" , err )
} else {
cfg . GeomapDefaultBaseLayerConfig = layer
2021-07-21 10:54:05 -05:00
}
}
2021-07-21 15:48:20 -05:00
cfg . GeomapEnableCustomBaseLayers = geomapSection . Key ( "enable_custom_baselayers" ) . MustBool ( true )
2021-07-21 10:54:05 -05:00
2024-11-04 10:35:31 -06:00
cfg . readRemoteCacheSettings ( )
2020-09-07 09:19:33 -05:00
cfg . readDateFormats ( )
2022-06-28 02:25:30 -05:00
cfg . readGrafanaJavascriptAgentConfig ( )
2020-09-07 09:19:33 -05:00
2021-05-27 14:03:18 -05:00
if err := cfg . readLiveSettings ( iniFile ) ; err != nil {
return err
}
2023-04-28 08:19:06 -05:00
databaseSection := iniFile . Section ( "database" )
cfg . DatabaseInstrumentQueries = databaseSection . Key ( "instrument_queries" ) . MustBool ( false )
2023-06-16 10:46:47 -05:00
logSection := iniFile . Section ( "log" )
cfg . UserFacingDefaultError = logSection . Key ( "user_facing_default_error" ) . MustString ( "please inspect Grafana server log for details" )
2023-07-24 15:12:59 -05:00
cfg . readFeatureManagementConfig ( )
2023-12-19 04:43:54 -06:00
cfg . readPublicDashboardsSettings ( )
2024-03-11 07:29:44 -05:00
cfg . readCloudMigrationSettings ( )
2023-07-24 15:12:59 -05:00
2024-03-05 09:41:19 -06:00
// read experimental scopes settings.
scopesSection := iniFile . Section ( "scopes" )
cfg . ScopesListScopesURL = scopesSection . Key ( "list_scopes_endpoint" ) . MustString ( "" )
cfg . ScopesListDashboardsURL = scopesSection . Key ( "list_dashboards_endpoint" ) . MustString ( "" )
2024-10-22 13:25:08 -05:00
// unified storage config
2024-08-30 04:59:42 -05:00
cfg . setUnifiedStorageConfig ( )
2020-08-26 06:11:57 -05:00
return nil
}
2020-09-08 04:33:04 -05:00
func valueAsString ( section * ini . Section , keyName string , defaultValue string ) string {
return section . Key ( keyName ) . MustString ( defaultValue )
2020-08-26 06:11:57 -05:00
}
2023-02-08 14:11:46 -06:00
func ( cfg * Cfg ) readSAMLConfig ( ) {
samlSec := cfg . Raw . Section ( "auth.saml" )
2023-03-22 12:41:59 -05:00
cfg . SAMLAuthEnabled = samlSec . Key ( "enabled" ) . MustBool ( false )
2023-02-08 14:11:46 -06:00
cfg . SAMLSkipOrgRoleSync = samlSec . Key ( "skip_org_role_sync" ) . MustBool ( false )
2023-08-03 11:02:40 -05:00
cfg . SAMLRoleValuesGrafanaAdmin = samlSec . Key ( "role_values_grafana_admin" ) . MustString ( "" )
2023-02-08 14:11:46 -06:00
}
2020-08-26 06:11:57 -05:00
func ( cfg * Cfg ) readLDAPConfig ( ) {
ldapSec := cfg . Raw . Section ( "auth.ldap" )
2023-02-10 12:01:55 -06:00
cfg . LDAPConfigFilePath = ldapSec . Key ( "config_file" ) . String ( )
cfg . LDAPSyncCron = ldapSec . Key ( "sync_cron" ) . String ( )
2023-03-22 12:41:59 -05:00
cfg . LDAPAuthEnabled = ldapSec . Key ( "enabled" ) . MustBool ( false )
2023-02-10 12:01:55 -06:00
cfg . LDAPSkipOrgRoleSync = ldapSec . Key ( "skip_org_role_sync" ) . MustBool ( false )
cfg . LDAPActiveSyncEnabled = ldapSec . Key ( "active_sync_enabled" ) . MustBool ( false )
cfg . LDAPAllowSignup = ldapSec . Key ( "allow_sign_up" ) . MustBool ( true )
2020-08-26 06:11:57 -05:00
}
2021-03-12 07:30:21 -06:00
func ( cfg * Cfg ) handleAWSConfig ( ) {
2021-02-24 11:08:13 -06:00
awsPluginSec := cfg . Raw . Section ( "aws" )
cfg . AWSAssumeRoleEnabled = awsPluginSec . Key ( "assume_role_enabled" ) . MustBool ( true )
2021-03-12 07:30:21 -06:00
allowedAuthProviders := awsPluginSec . Key ( "allowed_auth_providers" ) . MustString ( "default,keys,credentials" )
2021-02-24 11:08:13 -06:00
for _ , authProvider := range strings . Split ( allowedAuthProviders , "," ) {
authProvider = strings . TrimSpace ( authProvider )
if authProvider != "" {
cfg . AWSAllowedAuthProviders = append ( cfg . AWSAllowedAuthProviders , authProvider )
}
}
2021-03-10 00:41:22 -06:00
cfg . AWSListMetricsPageLimit = awsPluginSec . Key ( "list_metrics_page_limit" ) . MustInt ( 500 )
2024-02-05 12:59:32 -06:00
cfg . AWSExternalId = awsPluginSec . Key ( "external_id" ) . Value ( )
cfg . AWSSessionDuration = awsPluginSec . Key ( "session_duration" ) . Value ( )
cfg . AWSForwardSettingsPlugins = util . SplitString ( awsPluginSec . Key ( "forward_settings_to_plugins" ) . String ( ) )
2021-03-12 07:30:21 -06:00
// Also set environment variables that can be used by core plugins
err := os . Setenv ( awsds . AssumeRoleEnabledEnvVarKeyName , strconv . FormatBool ( cfg . AWSAssumeRoleEnabled ) )
if err != nil {
cfg . Logger . Error ( fmt . Sprintf ( "could not set environment variable '%s'" , awsds . AssumeRoleEnabledEnvVarKeyName ) , err )
}
err = os . Setenv ( awsds . AllowedAuthProvidersEnvVarKeyName , allowedAuthProviders )
if err != nil {
cfg . Logger . Error ( fmt . Sprintf ( "could not set environment variable '%s'" , awsds . AllowedAuthProvidersEnvVarKeyName ) , err )
}
2023-08-04 15:06:01 -05:00
2024-02-05 12:59:32 -06:00
err = os . Setenv ( awsds . ListMetricsPageLimitKeyName , strconv . Itoa ( cfg . AWSListMetricsPageLimit ) )
if err != nil {
cfg . Logger . Error ( fmt . Sprintf ( "could not set environment variable '%s'" , awsds . ListMetricsPageLimitKeyName ) , err )
}
2023-08-14 12:42:30 -05:00
err = os . Setenv ( awsds . GrafanaAssumeRoleExternalIdKeyName , cfg . AWSExternalId )
2023-08-04 15:06:01 -05:00
if err != nil {
cfg . Logger . Error ( fmt . Sprintf ( "could not set environment variable '%s'" , awsds . GrafanaAssumeRoleExternalIdKeyName ) , err )
}
2024-02-05 12:59:32 -06:00
err = os . Setenv ( awsds . SessionDurationEnvVarKeyName , cfg . AWSSessionDuration )
if err != nil {
cfg . Logger . Error ( fmt . Sprintf ( "could not set environment variable '%s'" , awsds . SessionDurationEnvVarKeyName ) , err )
}
2021-02-24 11:08:13 -06:00
}
2020-08-26 06:11:57 -05:00
func ( cfg * Cfg ) readSessionConfig ( ) {
sec , _ := cfg . Raw . GetSection ( "session" )
if sec != nil {
cfg . Logger . Warn (
"[Removed] Session setting was removed in v6.2, use remote_cache option instead" ,
)
}
}
func ( cfg * Cfg ) initLogging ( file * ini . File ) error {
2020-09-08 04:33:04 -05:00
logModeStr := valueAsString ( file . Section ( "log" ) , "mode" , "console" )
2020-08-26 06:11:57 -05:00
// split on comma
logModes := strings . Split ( logModeStr , "," )
// also try space
if len ( logModes ) == 1 {
logModes = strings . Split ( logModeStr , " " )
}
2020-09-08 04:33:04 -05:00
logsPath := valueAsString ( file . Section ( "paths" ) , "logs" , "" )
2024-01-23 05:36:22 -06:00
cfg . LogsPath = makeAbsolute ( logsPath , cfg . HomePath )
2020-08-26 06:11:57 -05:00
return log . ReadLoggingConfig ( logModes , cfg . LogsPath , file )
}
func ( cfg * Cfg ) LogConfigSources ( ) {
var text bytes . Buffer
2024-01-23 05:36:22 -06:00
for _ , file := range cfg . configFiles {
2020-08-26 06:11:57 -05:00
cfg . Logger . Info ( "Config loaded from" , "file" , file )
}
2024-01-23 05:36:22 -06:00
if len ( cfg . appliedCommandLineProperties ) > 0 {
for _ , prop := range cfg . appliedCommandLineProperties {
2020-08-26 06:11:57 -05:00
cfg . Logger . Info ( "Config overridden from command line" , "arg" , prop )
}
}
2024-01-23 05:36:22 -06:00
if len ( cfg . appliedEnvOverrides ) > 0 {
2020-08-26 06:11:57 -05:00
text . WriteString ( "\tEnvironment variables used:\n" )
2024-01-23 05:36:22 -06:00
for _ , prop := range cfg . appliedEnvOverrides {
2020-08-26 06:11:57 -05:00
cfg . Logger . Info ( "Config overridden from Environment variable" , "var" , prop )
}
}
2023-03-06 13:06:52 -06:00
cfg . Logger . Info ( "Target" , "target" , cfg . Target )
2024-01-23 05:36:22 -06:00
cfg . Logger . Info ( "Path Home" , "path" , cfg . HomePath )
2020-08-26 06:11:57 -05:00
cfg . Logger . Info ( "Path Data" , "path" , cfg . DataPath )
cfg . Logger . Info ( "Path Logs" , "path" , cfg . LogsPath )
2021-02-10 06:31:47 -06:00
cfg . Logger . Info ( "Path Plugins" , "path" , cfg . PluginsPath )
2020-08-26 06:11:57 -05:00
cfg . Logger . Info ( "Path Provisioning" , "path" , cfg . ProvisioningPath )
2020-12-11 04:44:44 -06:00
cfg . Logger . Info ( "App mode " + cfg . Env )
2020-08-26 06:11:57 -05:00
}
type DynamicSection struct {
section * ini . Section
Logger log . Logger
2024-02-05 09:25:54 -06:00
env osutil . Env
2020-08-26 06:11:57 -05:00
}
// Key dynamically overrides keys with environment variables.
// As a side effect, the value of the setting key will be updated if an environment variable is present.
func ( s * DynamicSection ) Key ( k string ) * ini . Key {
2021-11-30 04:28:52 -06:00
envKey := EnvKey ( s . section . Name ( ) , k )
2024-02-05 09:25:54 -06:00
envValue := s . env . Getenv ( envKey )
2020-08-26 06:11:57 -05:00
key := s . section . Key ( k )
if len ( envValue ) == 0 {
return key
}
2017-01-16 05:43:59 -06:00
2020-08-26 06:11:57 -05:00
key . SetValue ( envValue )
2021-05-10 10:03:10 -05:00
s . Logger . Info ( "Config overridden from Environment variable" , "var" , fmt . Sprintf ( "%s=%s" , envKey , RedactedValue ( envKey , envValue ) ) )
2020-08-26 06:11:57 -05:00
return key
}
2023-11-15 14:26:28 -06:00
func ( s * DynamicSection ) KeysHash ( ) map [ string ] string {
hash := s . section . KeysHash ( )
for k := range hash {
envKey := EnvKey ( s . section . Name ( ) , k )
2024-02-05 09:25:54 -06:00
envValue := s . env . Getenv ( envKey )
2023-11-15 14:26:28 -06:00
if len ( envValue ) > 0 {
hash [ k ] = envValue
}
}
return hash
}
2020-08-26 06:11:57 -05:00
// SectionWithEnvOverrides dynamically overrides keys with environment variables.
// As a side effect, the value of the setting key will be updated if an environment variable is present.
func ( cfg * Cfg ) SectionWithEnvOverrides ( s string ) * DynamicSection {
2024-02-05 09:25:54 -06:00
return & DynamicSection {
section : cfg . Raw . Section ( s ) ,
Logger : cfg . Logger ,
env : osutil . RealEnv { } ,
}
2020-08-26 06:11:57 -05:00
}
func readSecuritySettings ( iniFile * ini . File , cfg * Cfg ) error {
2018-04-30 09:21:04 -05:00
security := iniFile . Section ( "security" )
2024-01-23 05:36:22 -06:00
cfg . SecretKey = valueAsString ( security , "secret_key" , "" )
cfg . DisableGravatar = security . Key ( "disable_gravatar" ) . MustBool ( true )
2018-04-30 09:21:04 -05:00
cfg . DisableBruteForceLoginProtection = security . Key ( "disable_brute_force_login_protection" ) . MustBool ( false )
2015-05-01 01:40:13 -05:00
2019-02-05 14:09:55 -06:00
CookieSecure = security . Key ( "cookie_secure" ) . MustBool ( false )
cfg . CookieSecure = CookieSecure
2020-09-08 04:33:04 -05:00
samesiteString := valueAsString ( security , "cookie_samesite" , "lax" )
2019-02-05 14:09:55 -06:00
2020-01-14 10:41:54 -06:00
if samesiteString == "disabled" {
CookieSameSiteDisabled = true
cfg . CookieSameSiteDisabled = CookieSameSiteDisabled
2019-02-05 14:09:55 -06:00
} else {
2020-01-14 10:41:54 -06:00
validSameSiteValues := map [ string ] http . SameSite {
"lax" : http . SameSiteLaxMode ,
"strict" : http . SameSiteStrictMode ,
"none" : http . SameSiteNoneMode ,
}
if samesite , ok := validSameSiteValues [ samesiteString ] ; ok {
CookieSameSiteMode = samesite
cfg . CookieSameSiteMode = CookieSameSiteMode
} else {
CookieSameSiteMode = http . SameSiteLaxMode
cfg . CookieSameSiteMode = CookieSameSiteMode
}
2019-02-05 14:09:55 -06:00
}
2020-12-11 04:44:44 -06:00
cfg . AllowEmbedding = security . Key ( "allow_embedding" ) . MustBool ( false )
2019-05-06 02:56:23 -05:00
2020-12-11 04:44:44 -06:00
cfg . ContentTypeProtectionHeader = security . Key ( "x_content_type_options" ) . MustBool ( true )
cfg . XSSProtectionHeader = security . Key ( "x_xss_protection" ) . MustBool ( true )
2024-09-10 09:45:27 -05:00
cfg . ActionsAllowPostURL = security . Key ( "actions_allow_post_url" ) . MustString ( "" )
2020-12-11 04:44:44 -06:00
cfg . StrictTransportSecurity = security . Key ( "strict_transport_security" ) . MustBool ( false )
cfg . StrictTransportSecurityMaxAge = security . Key ( "strict_transport_security_max_age_seconds" ) . MustInt ( 86400 )
cfg . StrictTransportSecurityPreload = security . Key ( "strict_transport_security_preload" ) . MustBool ( false )
cfg . StrictTransportSecuritySubDomains = security . Key ( "strict_transport_security_subdomains" ) . MustBool ( false )
2024-03-22 11:09:53 -05:00
cfg . AngularSupportEnabled = security . Key ( "angular_support_enabled" ) . MustBool ( false )
2021-01-12 00:42:32 -06:00
cfg . CSPEnabled = security . Key ( "content_security_policy" ) . MustBool ( false )
cfg . CSPTemplate = security . Key ( "content_security_policy_template" ) . MustString ( "" )
2022-11-16 11:11:26 -06:00
cfg . CSPReportOnlyEnabled = security . Key ( "content_security_policy_report_only" ) . MustBool ( false )
cfg . CSPReportOnlyTemplate = security . Key ( "content_security_policy_report_only_template" ) . MustString ( "" )
2024-10-17 09:56:50 -05:00
enableFrontendSandboxForPlugins := security . Key ( "enable_frontend_sandbox_for_plugins" ) . MustString ( "" )
for _ , plug := range strings . Split ( enableFrontendSandboxForPlugins , "," ) {
2023-07-05 04:16:56 -05:00
plug = strings . TrimSpace ( plug )
2024-10-17 09:56:50 -05:00
cfg . EnableFrontendSandboxForPlugins = append ( cfg . EnableFrontendSandboxForPlugins , plug )
2023-07-05 04:16:56 -05:00
}
2022-11-16 11:11:26 -06:00
if cfg . CSPEnabled && cfg . CSPTemplate == "" {
return fmt . Errorf ( "enabling content_security_policy requires a content_security_policy_template configuration" )
}
if cfg . CSPReportOnlyEnabled && cfg . CSPReportOnlyTemplate == "" {
return fmt . Errorf ( "enabling content_security_policy_report_only requires a content_security_policy_report_only_template configuration" )
}
2019-06-12 06:15:50 -05:00
2020-08-26 06:11:57 -05:00
// read data source proxy whitelist
2024-01-23 05:36:22 -06:00
cfg . DataProxyWhiteList = make ( map [ string ] bool )
2020-09-08 04:33:04 -05:00
securityStr := valueAsString ( security , "data_source_proxy_whitelist" , "" )
2020-08-26 06:11:57 -05:00
for _ , hostAndIP := range util . SplitString ( securityStr ) {
2024-01-23 05:36:22 -06:00
cfg . DataProxyWhiteList [ hostAndIP ] = true
2015-09-09 10:21:25 -05:00
}
2015-01-27 08:45:27 -06:00
// admin
2019-11-08 04:11:03 -06:00
cfg . DisableInitAdminCreation = security . Key ( "disable_initial_admin_creation" ) . MustBool ( false )
2020-12-15 12:09:04 -06:00
cfg . AdminUser = valueAsString ( security , "admin_user" , "" )
cfg . AdminPassword = valueAsString ( security , "admin_password" , "" )
2022-09-07 07:38:40 -05:00
cfg . AdminEmail = valueAsString ( security , "admin_email" , fmt . Sprintf ( "%s@localhost" , cfg . AdminUser ) )
2015-01-27 08:45:27 -06:00
2020-08-26 06:11:57 -05:00
return nil
}
2023-01-30 04:54:14 -06:00
2020-09-14 08:57:38 -05:00
func readAuthSettings ( iniFile * ini . File , cfg * Cfg ) ( err error ) {
2018-04-30 09:21:04 -05:00
auth := iniFile . Section ( "auth" )
2019-02-05 14:09:55 -06:00
2020-12-15 12:09:04 -06:00
cfg . LoginCookieName = valueAsString ( auth , "login_cookie_name" , "grafana_session" )
2022-06-01 05:29:15 -05:00
const defaultMaxInactiveLifetime = "7d"
maxInactiveDurationVal := valueAsString ( auth , "login_maximum_inactive_lifetime_duration" , defaultMaxInactiveLifetime )
2020-11-02 12:26:19 -06:00
cfg . LoginMaxInactiveLifetime , err = gtime . ParseDuration ( maxInactiveDurationVal )
2020-09-14 08:57:38 -05:00
if err != nil {
return err
}
2023-06-06 11:14:11 -05:00
cfg . OAuthAllowInsecureEmailLookup = auth . Key ( "oauth_allow_insecure_email_lookup" ) . MustBool ( false )
2022-06-01 05:29:15 -05:00
const defaultMaxLifetime = "30d"
maxLifetimeDurationVal := valueAsString ( auth , "login_maximum_lifetime_duration" , defaultMaxLifetime )
2020-11-02 12:26:19 -06:00
cfg . LoginMaxLifetime , err = gtime . ParseDuration ( maxLifetimeDurationVal )
2020-09-14 08:57:38 -05:00
if err != nil {
return err
}
2019-02-05 14:09:55 -06:00
2019-06-26 01:47:03 -05:00
cfg . ApiKeyMaxSecondsToLive = auth . Key ( "api_key_max_seconds_to_live" ) . MustInt64 ( - 1 )
2019-02-05 14:09:55 -06:00
cfg . TokenRotationIntervalMinutes = auth . Key ( "token_rotation_interval_minutes" ) . MustInt ( 10 )
if cfg . TokenRotationIntervalMinutes < 2 {
cfg . TokenRotationIntervalMinutes = 2
}
2023-02-27 08:28:49 -06:00
cfg . DisableLoginForm = auth . Key ( "disable_login_form" ) . MustBool ( false )
2024-01-23 05:36:22 -06:00
cfg . DisableSignoutMenu = auth . Key ( "disable_signout_menu" ) . MustBool ( false )
2023-01-19 08:53:02 -06:00
// Deprecated
2023-02-27 08:28:49 -06:00
cfg . OAuthAutoLogin = auth . Key ( "oauth_auto_login" ) . MustBool ( false )
if cfg . OAuthAutoLogin {
2023-01-19 08:53:02 -06:00
cfg . Logger . Warn ( "[Deprecated] The oauth_auto_login configuration setting is deprecated. Please use auto_login inside auth provider section instead." )
}
2024-09-27 05:11:27 -05:00
// Default to the translation key used in the frontend
cfg . OAuthLoginErrorMessage = valueAsString ( auth , "oauth_login_error_message" , "oauth.login.error" )
2020-09-01 03:57:43 -05:00
cfg . OAuthCookieMaxAge = auth . Key ( "oauth_state_cookie_max_age" ) . MustInt ( 600 )
2024-08-19 11:57:37 -05:00
cfg . OAuthRefreshTokenServerLockMinWaitMs = auth . Key ( "oauth_refresh_token_server_lock_min_wait_ms" ) . MustInt64 ( 1000 )
2023-08-29 04:34:11 -05:00
cfg . SignoutRedirectUrl = valueAsString ( auth , "signout_redirect_url" , "" )
2024-03-22 09:34:05 -05:00
2023-02-07 09:28:40 -06:00
// Deprecated
2024-03-22 09:34:05 -05:00
cfg . OAuthSkipOrgRoleUpdateSync = false
2015-01-27 08:14:53 -06:00
2022-10-12 10:34:59 -05:00
cfg . DisableLogin = auth . Key ( "disable_login" ) . MustBool ( false )
2020-10-08 03:03:20 -05:00
// SigV4
2024-01-23 05:36:22 -06:00
cfg . SigV4AuthEnabled = auth . Key ( "sigv4_auth_enabled" ) . MustBool ( false )
2022-02-08 07:48:17 -06:00
cfg . SigV4VerboseLogging = auth . Key ( "sigv4_verbose_logging" ) . MustBool ( false )
2020-10-08 03:03:20 -05:00
2022-08-11 09:12:57 -05:00
// Azure Auth
2024-01-23 05:36:22 -06:00
cfg . AzureAuthEnabled = auth . Key ( "azure_auth_enabled" ) . MustBool ( false )
2023-11-29 08:51:13 -06:00
// ID response header
cfg . IDResponseHeaderEnabled = auth . Key ( "id_response_header_enabled" ) . MustBool ( false )
2023-12-21 07:06:28 -06:00
cfg . IDResponseHeaderPrefix = auth . Key ( "id_response_header_prefix" ) . MustString ( "X-Grafana" )
2023-11-29 08:51:13 -06:00
idHeaderNamespaces := util . SplitString ( auth . Key ( "id_response_header_namespaces" ) . MustString ( "" ) )
cfg . IDResponseHeaderNamespaces = make ( map [ string ] struct { } , len ( idHeaderNamespaces ) )
for _ , namespace := range idHeaderNamespaces {
cfg . IDResponseHeaderNamespaces [ namespace ] = struct { } { }
}
2015-01-27 08:45:27 -06:00
// anonymous access
2023-12-12 04:57:25 -06:00
anonSection := iniFile . Section ( "auth.anonymous" )
cfg . AnonymousEnabled = anonSection . Key ( "enabled" ) . MustBool ( false )
cfg . AnonymousOrgName = valueAsString ( anonSection , "org_name" , "" )
cfg . AnonymousOrgRole = valueAsString ( anonSection , "org_role" , "" )
cfg . AnonymousHideVersion = anonSection . Key ( "hide_version" ) . MustBool ( false )
cfg . AnonymousDeviceLimit = anonSection . Key ( "device_limit" ) . MustInt64 ( 0 )
2015-01-07 09:37:24 -06:00
2020-08-26 06:11:57 -05:00
// basic auth
authBasic := iniFile . Section ( "auth.basic" )
2023-02-27 08:28:49 -06:00
cfg . BasicAuthEnabled = authBasic . Key ( "enabled" ) . MustBool ( true )
2024-02-16 04:58:05 -06:00
cfg . BasicAuthStrongPasswordPolicy = authBasic . Key ( "password_policy" ) . MustBool ( false )
2020-08-26 06:11:57 -05:00
2024-01-12 02:24:16 -06:00
// SSO Settings
ssoSettings := iniFile . Section ( "sso_settings" )
cfg . SSOSettingsReloadInterval = ssoSettings . Key ( "reload_interval" ) . MustDuration ( 1 * time . Minute )
2024-01-23 08:48:06 -06:00
providers := ssoSettings . Key ( "configurable_providers" ) . String ( )
2024-01-12 02:24:16 -06:00
2024-01-23 08:48:06 -06:00
cfg . SSOSettingsConfigurableProviders = make ( map [ string ] bool )
for _ , provider := range util . SplitString ( providers ) {
cfg . SSOSettingsConfigurableProviders [ provider ] = true
}
2024-09-27 02:11:59 -05:00
// Managed Service Accounts
cfg . ManagedServiceAccountsEnabled = auth . Key ( "managed_service_accounts_enabled" ) . MustBool ( false )
2020-08-26 06:11:57 -05:00
return nil
}
2023-05-25 08:38:30 -05:00
func readOAuth2ServerSettings ( cfg * Cfg ) {
oauth2Srv := cfg . SectionWithEnvOverrides ( "oauth2_server" )
cfg . OAuth2ServerEnabled = oauth2Srv . Key ( "enabled" ) . MustBool ( false )
cfg . OAuth2ServerGeneratedKeyTypeForClient = strings . ToUpper ( oauth2Srv . Key ( "generated_key_type_for_client" ) . In ( "ECDSA" , [ ] string { "RSA" , "ECDSA" } ) )
cfg . OAuth2ServerAccessTokenLifespan = oauth2Srv . Key ( "access_token_lifespan" ) . MustDuration ( time . Minute * 3 )
}
2020-08-26 06:11:57 -05:00
func readUserSettings ( iniFile * ini . File , cfg * Cfg ) error {
users := iniFile . Section ( "users" )
2024-01-23 05:36:22 -06:00
cfg . AllowUserSignUp = users . Key ( "allow_sign_up" ) . MustBool ( true )
cfg . AllowUserOrgCreate = users . Key ( "allow_org_create" ) . MustBool ( true )
2021-01-07 04:36:13 -06:00
cfg . AutoAssignOrg = users . Key ( "auto_assign_org" ) . MustBool ( true )
cfg . AutoAssignOrgId = users . Key ( "auto_assign_org_id" ) . MustInt ( 1 )
2024-03-12 03:35:13 -05:00
cfg . LoginDefaultOrgId = users . Key ( "login_default_org_id" ) . MustInt64 ( - 1 )
2023-07-17 08:58:16 -05:00
cfg . AutoAssignOrgRole = users . Key ( "auto_assign_org_role" ) . In (
2024-06-12 23:11:35 -05:00
string ( identity . RoleViewer ) , [ ] string {
string ( identity . RoleNone ) ,
string ( identity . RoleViewer ) ,
string ( identity . RoleEditor ) ,
string ( identity . RoleAdmin ) } )
2024-01-23 05:36:22 -06:00
cfg . VerifyEmailEnabled = users . Key ( "verify_email_enabled" ) . MustBool ( false )
2020-09-08 04:33:04 -05:00
2024-03-22 10:45:18 -05:00
// Deprecated
// cfg.CaseInsensitiveLogin = users.Key("case_insensitive_login").MustBool(true)
cfg . CaseInsensitiveLogin = true
2022-06-24 09:59:45 -05:00
2024-01-23 05:36:22 -06:00
cfg . LoginHint = valueAsString ( users , "login_hint" , "" )
cfg . PasswordHint = valueAsString ( users , "password_hint" , "" )
2020-12-11 04:44:44 -06:00
cfg . DefaultTheme = valueAsString ( users , "default_theme" , "" )
2022-11-22 06:18:34 -06:00
cfg . DefaultLanguage = valueAsString ( users , "default_language" , "" )
2021-04-13 08:27:51 -05:00
cfg . HomePage = valueAsString ( users , "home_page" , "" )
2024-01-23 05:36:22 -06:00
cfg . ExternalUserMngLinkUrl = valueAsString ( users , "external_manage_link_url" , "" )
cfg . ExternalUserMngLinkName = valueAsString ( users , "external_manage_link_name" , "" )
cfg . ExternalUserMngInfo = valueAsString ( users , "external_manage_info" , "" )
2020-09-08 04:33:04 -05:00
2023-03-16 04:54:01 -05:00
cfg . ViewersCanEdit = users . Key ( "viewers_can_edit" ) . MustBool ( false )
2020-08-26 06:11:57 -05:00
cfg . EditorsCanAdmin = users . Key ( "editors_can_admin" ) . MustBool ( false )
2018-05-07 03:39:16 -05:00
2020-10-13 05:30:09 -05:00
userInviteMaxLifetimeVal := valueAsString ( users , "user_invite_max_lifetime_duration" , "24h" )
2020-11-02 12:26:19 -06:00
userInviteMaxLifetimeDuration , err := gtime . ParseDuration ( userInviteMaxLifetimeVal )
2020-10-13 05:30:09 -05:00
if err != nil {
return err
}
cfg . UserInviteMaxLifetime = userInviteMaxLifetimeDuration
if cfg . UserInviteMaxLifetime < time . Minute * 15 {
return errors . New ( "the minimum supported value for the `user_invite_max_lifetime_duration` configuration is 15m (15 minutes)" )
}
2024-06-24 09:54:56 -05:00
cfg . UserLastSeenUpdateInterval , err = gtime . ParseDuration ( valueAsString ( users , "last_seen_update_interval" , "15m" ) )
if err != nil {
return err
}
if cfg . UserLastSeenUpdateInterval < time . Minute * 5 {
cfg . Logger . Warn ( "the minimum supported value for the `last_seen_update_interval` configuration is 5m (5 minutes)" )
cfg . UserLastSeenUpdateInterval = time . Minute * 5
} else if cfg . UserLastSeenUpdateInterval > time . Hour * 1 {
cfg . Logger . Warn ( "the maximum supported value for the `last_seen_update_interval` configuration is 1h (1 hour)" )
cfg . UserLastSeenUpdateInterval = time . Hour * 1
}
2020-11-24 05:10:32 -06:00
cfg . HiddenUsers = make ( map [ string ] struct { } )
hiddenUsers := users . Key ( "hidden_users" ) . MustString ( "" )
for _ , user := range strings . Split ( hiddenUsers , "," ) {
user = strings . TrimSpace ( user )
2021-02-12 09:08:18 -06:00
if user != "" {
cfg . HiddenUsers [ user ] = struct { } { }
}
2020-11-24 05:10:32 -06:00
}
2024-02-16 11:54:59 -06:00
verificationEmailMaxLifetimeVal := valueAsString ( users , "verification_email_max_lifetime_duration" , "1h" )
verificationEmailMaxLifetimeDuration , err := gtime . ParseDuration ( verificationEmailMaxLifetimeVal )
if err != nil {
return err
}
cfg . VerificationEmailMaxLifetime = verificationEmailMaxLifetimeDuration
2020-08-26 06:11:57 -05:00
return nil
}
2015-06-30 02:37:52 -05:00
2022-11-22 03:08:40 -06:00
func readServiceAccountSettings ( iniFile * ini . File , cfg * Cfg ) error {
serviceAccount := iniFile . Section ( "service_accounts" )
cfg . SATokenExpirationDayLimit = serviceAccount . Key ( "token_expiration_day_limit" ) . MustInt ( - 1 )
return nil
}
2021-08-25 08:11:22 -05:00
func ( cfg * Cfg ) readRenderingSettings ( iniFile * ini . File ) error {
2018-05-24 08:26:27 -05:00
renderSec := iniFile . Section ( "rendering" )
2020-09-08 04:33:04 -05:00
cfg . RendererUrl = valueAsString ( renderSec , "server_url" , "" )
cfg . RendererCallbackUrl = valueAsString ( renderSec , "callback_url" , "" )
2022-08-30 05:09:38 -05:00
cfg . RendererAuthToken = valueAsString ( renderSec , "renderer_token" , "-" )
2020-09-08 04:33:04 -05:00
2018-09-04 06:42:55 -05:00
if cfg . RendererCallbackUrl == "" {
cfg . RendererCallbackUrl = AppUrl
} else {
if cfg . RendererCallbackUrl [ len ( cfg . RendererCallbackUrl ) - 1 ] != '/' {
cfg . RendererCallbackUrl += "/"
}
_ , err := url . Parse ( cfg . RendererCallbackUrl )
if err != nil {
2020-08-26 06:11:57 -05:00
// XXX: Should return an error?
2021-11-08 10:56:56 -06:00
cfg . Logger . Error ( "Invalid callback_url." , "url" , cfg . RendererCallbackUrl , "error" , err )
2021-10-26 10:36:24 -05:00
os . Exit ( 1 )
2018-09-04 06:42:55 -05:00
}
}
2020-04-27 10:25:08 -05:00
2020-09-08 04:33:04 -05:00
cfg . RendererConcurrentRequestLimit = renderSec . Key ( "concurrent_render_request_limit" ) . MustInt ( 30 )
2022-11-03 06:06:55 -05:00
cfg . RendererRenderKeyLifeTime = renderSec . Key ( "render_key_lifetime" ) . MustDuration ( 5 * time . Minute )
2024-02-26 06:27:34 -06:00
cfg . RendererDefaultImageWidth = renderSec . Key ( "default_image_width" ) . MustInt ( 1000 )
cfg . RendererDefaultImageHeight = renderSec . Key ( "default_image_height" ) . MustInt ( 500 )
cfg . RendererDefaultImageScale = renderSec . Key ( "default_image_scale" ) . MustFloat64 ( 1 )
2018-10-12 00:55:36 -05:00
cfg . ImagesDir = filepath . Join ( cfg . DataPath , "png" )
2021-05-12 10:16:57 -05:00
cfg . CSVsDir = filepath . Join ( cfg . DataPath , "csv" )
2024-02-08 06:09:34 -06:00
cfg . PDFsDir = filepath . Join ( cfg . DataPath , "pdf" )
2014-11-14 10:13:33 -06:00
2020-08-26 06:11:57 -05:00
return nil
}
2021-09-29 09:16:40 -05:00
2024-01-23 05:36:22 -06:00
func ( cfg * Cfg ) readAlertingSettings ( iniFile * ini . File ) error {
2024-03-07 15:01:11 -06:00
// This check is kept to prevent users that upgrade to Grafana 11 with the legacy alerting enabled. This should prevent them from accidentally upgrading without migration to Unified Alerting.
2018-04-30 09:21:04 -05:00
alerting := iniFile . Section ( "alerting" )
2021-11-24 13:56:07 -06:00
enabled , err := alerting . Key ( "enabled" ) . Bool ( )
2024-03-07 15:01:11 -06:00
if err == nil && enabled {
cfg . Logger . Error ( "Option '[alerting].enabled' cannot be true. Legacy Alerting is removed. It is no longer deployed, enhanced, or supported. Delete '[alerting].enabled' and use '[unified_alerting].enabled' to enable Grafana Alerting. For more information, refer to the documentation on upgrading to Grafana Alerting (https://grafana.com/docs/grafana/v10.4/alerting/set-up/migrating-alerts)" )
return fmt . Errorf ( "invalid setting [alerting].enabled" )
2021-11-24 13:56:07 -06:00
}
2020-08-26 06:11:57 -05:00
return nil
}
2019-04-12 06:46:42 -05:00
2022-09-26 15:25:34 -05:00
func readGRPCServerSettings ( cfg * Cfg , iniFile * ini . File ) error {
server := iniFile . Section ( "grpc_server" )
errPrefix := "grpc_server:"
useTLS := server . Key ( "use_tls" ) . MustBool ( false )
certFile := server . Key ( "cert_file" ) . String ( )
keyFile := server . Key ( "cert_key" ) . String ( )
if useTLS {
serverCert , err := tls . LoadX509KeyPair ( certFile , keyFile )
if err != nil {
return fmt . Errorf ( "%s error loading X509 key pair: %w" , errPrefix , err )
}
cfg . GRPCServerTLSConfig = & tls . Config {
Certificates : [ ] tls . Certificate { serverCert } ,
ClientAuth : tls . NoClientCert ,
}
}
cfg . GRPCServerNetwork = valueAsString ( server , "network" , "tcp" )
cfg . GRPCServerAddress = valueAsString ( server , "address" , "" )
2024-04-16 09:30:51 -05:00
cfg . GRPCServerEnableLogging = server . Key ( "enable_logging" ) . MustBool ( false )
2024-04-30 09:18:03 -05:00
cfg . GRPCServerMaxRecvMsgSize = server . Key ( "max_recv_msg_size" ) . MustInt ( 0 )
cfg . GRPCServerMaxSendMsgSize = server . Key ( "max_send_msg_size" ) . MustInt ( 0 )
2022-09-26 15:25:34 -05:00
switch cfg . GRPCServerNetwork {
case "unix" :
if cfg . GRPCServerAddress != "" {
// Explicitly provided path for unix domain socket.
if stat , err := os . Stat ( cfg . GRPCServerAddress ) ; os . IsNotExist ( err ) {
// File does not exist - nice, nothing to do.
} else if err != nil {
return fmt . Errorf ( "%s error getting stat for a file: %s" , errPrefix , cfg . GRPCServerAddress )
} else {
if stat . Mode ( ) & fs . ModeSocket == 0 {
return fmt . Errorf ( "%s file %s already exists and is not a unix domain socket" , errPrefix , cfg . GRPCServerAddress )
}
// Unix domain socket file, should be safe to remove.
err := os . Remove ( cfg . GRPCServerAddress )
if err != nil {
return fmt . Errorf ( "%s can't remove unix socket file: %s" , errPrefix , cfg . GRPCServerAddress )
}
}
} else {
// Use temporary file path for a unix domain socket.
tf , err := os . CreateTemp ( "" , "gf_grpc_server_api" )
if err != nil {
return fmt . Errorf ( "%s error creating tmp file: %v" , errPrefix , err )
}
unixPath := tf . Name ( )
if err := tf . Close ( ) ; err != nil {
return fmt . Errorf ( "%s error closing tmp file: %v" , errPrefix , err )
}
if err := os . Remove ( unixPath ) ; err != nil {
return fmt . Errorf ( "%s error removing tmp file: %v" , errPrefix , err )
}
cfg . GRPCServerAddress = unixPath
}
case "tcp" :
if cfg . GRPCServerAddress == "" {
cfg . GRPCServerAddress = "127.0.0.1:10000"
}
default :
return fmt . Errorf ( "%s unsupported network %s" , errPrefix , cfg . GRPCServerNetwork )
}
return nil
}
2020-12-11 04:44:44 -06:00
func readSnapshotsSettings ( cfg * Cfg , iniFile * ini . File ) error {
2020-08-26 06:11:57 -05:00
snapshots := iniFile . Section ( "snapshots" )
2020-09-08 04:33:04 -05:00
2023-01-26 07:28:11 -06:00
cfg . SnapshotEnabled = snapshots . Key ( "enabled" ) . MustBool ( true )
cfg . ExternalSnapshotUrl = valueAsString ( snapshots , "external_snapshot_url" , "" )
cfg . ExternalSnapshotName = valueAsString ( snapshots , "external_snapshot_name" , "" )
2020-09-08 04:33:04 -05:00
2023-01-26 07:28:11 -06:00
cfg . ExternalEnabled = snapshots . Key ( "external_enabled" ) . MustBool ( true )
2020-12-11 04:44:44 -06:00
cfg . SnapshotPublicMode = snapshots . Key ( "public_mode" ) . MustBool ( false )
2016-05-27 06:52:19 -05:00
2020-08-26 06:11:57 -05:00
return nil
}
2018-11-01 06:07:11 -05:00
2021-02-01 03:13:09 -06:00
func ( cfg * Cfg ) readServerSettings ( iniFile * ini . File ) error {
2020-08-26 06:11:57 -05:00
server := iniFile . Section ( "server" )
var err error
2021-11-08 10:56:56 -06:00
AppUrl , AppSubUrl , err = cfg . parseAppUrlAndSubUrl ( server )
2019-04-25 01:29:07 -05:00
if err != nil {
return err
}
2018-11-01 06:07:11 -05:00
2020-11-13 02:52:38 -06:00
cfg . AppURL = AppUrl
cfg . AppSubURL = AppSubUrl
2020-12-11 04:44:44 -06:00
cfg . Protocol = HTTPScheme
2024-01-23 05:36:22 -06:00
cfg . ServeFromSubPath = server . Key ( "serve_from_sub_path" ) . MustBool ( false )
2024-03-22 10:13:22 -05:00
cfg . CertWatchInterval = server . Key ( "certs_watch_interval" ) . MustDuration ( 0 )
2021-02-01 03:13:09 -06:00
2020-09-08 04:33:04 -05:00
protocolStr := valueAsString ( server , "protocol" , "http" )
2020-08-26 06:11:57 -05:00
if protocolStr == "https" {
2020-12-11 04:44:44 -06:00
cfg . Protocol = HTTPSScheme
2021-03-10 05:41:29 -06:00
cfg . CertFile = server . Key ( "cert_file" ) . String ( )
cfg . KeyFile = server . Key ( "cert_key" ) . String ( )
2024-08-06 17:18:32 -05:00
cfg . CertPassword = server . Key ( "cert_pass" ) . String ( )
2019-04-25 01:29:07 -05:00
}
2020-08-26 06:11:57 -05:00
if protocolStr == "h2" {
2020-12-11 04:44:44 -06:00
cfg . Protocol = HTTP2Scheme
2021-03-10 05:41:29 -06:00
cfg . CertFile = server . Key ( "cert_file" ) . String ( )
cfg . KeyFile = server . Key ( "cert_key" ) . String ( )
2024-08-06 17:18:32 -05:00
cfg . CertPassword = server . Key ( "cert_pass" ) . String ( )
2019-03-03 14:48:00 -06:00
}
2020-08-26 06:11:57 -05:00
if protocolStr == "socket" {
2020-12-11 04:44:44 -06:00
cfg . Protocol = SocketScheme
2022-11-01 09:04:01 -05:00
cfg . SocketGid = server . Key ( "socket_gid" ) . MustInt ( - 1 )
cfg . SocketMode = server . Key ( "socket_mode" ) . MustInt ( 0660 )
2020-12-11 04:44:44 -06:00
cfg . SocketPath = server . Key ( "socket" ) . String ( )
2015-04-08 01:59:12 -05:00
}
2015-03-03 03:18:24 -06:00
2023-05-08 10:11:36 -05:00
cfg . MinTLSVersion = valueAsString ( server , "min_tls_version" , "TLS1.2" )
if cfg . MinTLSVersion == "TLS1.0" || cfg . MinTLSVersion == "TLS1.1" {
return fmt . Errorf ( "TLS version not configured correctly:%v, allowed values are TLS1.2 and TLS1.3" , cfg . MinTLSVersion )
}
2020-12-11 04:44:44 -06:00
cfg . Domain = valueAsString ( server , "domain" , "localhost" )
2021-03-10 05:41:29 -06:00
cfg . HTTPAddr = valueAsString ( server , "http_addr" , DefaultHTTPAddr )
cfg . HTTPPort = valueAsString ( server , "http_port" , "3000" )
2020-12-11 04:44:44 -06:00
cfg . RouterLogging = server . Key ( "router_logging" ) . MustBool ( false )
2015-04-09 05:16:59 -05:00
2021-03-10 05:41:29 -06:00
cfg . EnableGzip = server . Key ( "enable_gzip" ) . MustBool ( false )
cfg . EnforceDomain = server . Key ( "enforce_domain" ) . MustBool ( false )
2020-09-08 04:33:04 -05:00
staticRoot := valueAsString ( server , "static_root_path" , "" )
2024-01-23 05:36:22 -06:00
cfg . StaticRootPath = makeAbsolute ( staticRoot , cfg . HomePath )
2020-01-10 08:33:54 -06:00
2020-08-26 06:11:57 -05:00
if err := cfg . validateStaticRootPath ( ) ; err != nil {
return err
2020-01-10 08:33:54 -06:00
}
2021-02-01 03:13:09 -06:00
cdnURL := valueAsString ( server , "cdn_url" , "" )
if cdnURL != "" {
cfg . CDNRootURL , err = url . Parse ( cdnURL )
if err != nil {
return err
}
}
2021-03-19 05:21:52 -05:00
cfg . ReadTimeout = server . Key ( "read_timeout" ) . MustDuration ( 0 )
2022-11-30 11:12:34 -06:00
headersSection := cfg . Raw . Section ( "server.custom_response_headers" )
keys := headersSection . Keys ( )
cfg . CustomResponseHeaders = make ( map [ string ] string , len ( keys ) )
for _ , key := range keys {
cfg . CustomResponseHeaders [ key . Name ( ) ] = key . Value ( )
}
2020-08-26 06:11:57 -05:00
return nil
2020-01-10 08:33:54 -06:00
}
2020-12-28 05:24:42 -06:00
2021-02-01 03:13:09 -06:00
// GetContentDeliveryURL returns full content delivery URL with /<edition>/<version> added to URL
2023-12-06 14:27:08 -06:00
func ( cfg * Cfg ) GetContentDeliveryURL ( prefix string ) ( string , error ) {
if cfg . CDNRootURL == nil {
return "" , nil
}
if cfg . BuildVersion == "" {
return "" , errors . New ( "BuildVersion is not set" )
2021-02-01 03:13:09 -06:00
}
2023-12-06 14:27:08 -06:00
url := * cfg . CDNRootURL
2021-02-01 03:13:09 -06:00
2023-12-06 14:27:08 -06:00
url . Path = path . Join ( url . Path , prefix , cfg . BuildVersion )
return url . String ( ) + "/" , nil
2021-02-01 03:13:09 -06:00
}
2020-12-28 05:24:42 -06:00
func ( cfg * Cfg ) readDataSourcesSettings ( ) {
datasources := cfg . Raw . Section ( "datasources" )
cfg . DataSourceLimit = datasources . Key ( "datasource_limit" ) . MustInt ( 5000 )
2024-01-29 04:47:28 -06:00
cfg . ConcurrentQueryCount = datasources . Key ( "concurrent_query_count" ) . MustInt ( 10 )
2020-12-28 05:24:42 -06:00
}
2021-05-27 14:03:18 -05:00
2024-01-31 11:09:24 -06:00
func ( cfg * Cfg ) readDataSourceSecuritySettings ( ) {
datasources := cfg . Raw . Section ( "datasources.ip_range_security" )
cfg . IPRangeACEnabled = datasources . Key ( "enabled" ) . MustBool ( false )
cfg . IPRangeACSecretKey = datasources . Key ( "secret_key" ) . MustString ( "" )
2024-03-06 06:40:48 -06:00
if cfg . IPRangeACEnabled && cfg . IPRangeACSecretKey == "" {
cfg . Logger . Error ( "IP range access control is enabled but no secret key is set" )
}
2024-01-31 11:09:24 -06:00
allowedURLString := datasources . Key ( "allow_list" ) . MustString ( "" )
2024-02-23 10:13:21 -06:00
for _ , urlString := range util . SplitString ( allowedURLString ) {
allowedURL , err := url . Parse ( urlString )
if err != nil {
cfg . Logger . Error ( "Error parsing allowed URL for IP range access control" , "error" , err )
continue
} else {
cfg . IPRangeACAllowedURLs = append ( cfg . IPRangeACAllowedURLs , allowedURL )
}
}
2024-01-31 11:09:24 -06:00
}
2023-04-17 03:44:05 -05:00
func ( cfg * Cfg ) readSqlDataSourceSettings ( ) {
sqlDatasources := cfg . Raw . Section ( "sql_datasources" )
cfg . SqlDatasourceMaxOpenConnsDefault = sqlDatasources . Key ( "max_open_conns_default" ) . MustInt ( 100 )
cfg . SqlDatasourceMaxIdleConnsDefault = sqlDatasources . Key ( "max_idle_conns_default" ) . MustInt ( 100 )
cfg . SqlDatasourceMaxConnLifetimeDefault = sqlDatasources . Key ( "max_conn_lifetime_default" ) . MustInt ( 14400 )
}
2021-07-01 01:30:09 -05:00
func GetAllowedOriginGlobs ( originPatterns [ ] string ) ( [ ] glob . Glob , error ) {
allowedOrigins := originPatterns
2023-01-23 10:44:27 -06:00
originGlobs := make ( [ ] glob . Glob , 0 , len ( allowedOrigins ) )
2021-07-01 01:30:09 -05:00
for _ , originPattern := range allowedOrigins {
g , err := glob . Compile ( originPattern )
if err != nil {
return nil , fmt . Errorf ( "error parsing origin pattern: %v" , err )
}
originGlobs = append ( originGlobs , g )
}
return originGlobs , nil
}
2021-05-27 14:03:18 -05:00
func ( cfg * Cfg ) readLiveSettings ( iniFile * ini . File ) error {
section := iniFile . Section ( "live" )
cfg . LiveMaxConnections = section . Key ( "max_connections" ) . MustInt ( 100 )
if cfg . LiveMaxConnections < - 1 {
return fmt . Errorf ( "unexpected value %d for [live] max_connections" , cfg . LiveMaxConnections )
}
2021-06-24 03:07:09 -05:00
cfg . LiveHAEngine = section . Key ( "ha_engine" ) . MustString ( "" )
switch cfg . LiveHAEngine {
case "" , "redis" :
default :
return fmt . Errorf ( "unsupported live HA engine type: %s" , cfg . LiveHAEngine )
}
2024-09-25 15:20:35 -05:00
cfg . LiveHAPrefix = section . Key ( "ha_prefix" ) . MustString ( "" )
2021-06-24 03:07:09 -05:00
cfg . LiveHAEngineAddress = section . Key ( "ha_engine_address" ) . MustString ( "127.0.0.1:6379" )
2023-10-11 03:45:24 -05:00
cfg . LiveHAEnginePassword = section . Key ( "ha_engine_password" ) . MustString ( "" )
2021-07-01 01:30:09 -05:00
allowedOrigins := section . Key ( "allowed_origins" ) . MustString ( "" )
2024-06-14 13:16:36 -05:00
origins := strings . Split ( allowedOrigins , "," )
originPatterns := make ( [ ] string , 0 , len ( origins ) )
for _ , originPattern := range origins {
2021-07-01 01:30:09 -05:00
originPattern = strings . TrimSpace ( originPattern )
if originPattern == "" {
continue
}
originPatterns = append ( originPatterns , originPattern )
}
2024-06-14 13:16:36 -05:00
2021-07-01 01:30:09 -05:00
_ , err := GetAllowedOriginGlobs ( originPatterns )
if err != nil {
return err
}
2024-06-14 13:16:36 -05:00
2021-07-01 01:30:09 -05:00
cfg . LiveAllowedOrigins = originPatterns
2021-05-27 14:03:18 -05:00
return nil
}
2023-12-19 04:43:54 -06:00
func ( cfg * Cfg ) readPublicDashboardsSettings ( ) {
publicDashboards := cfg . Raw . Section ( "public_dashboards" )
cfg . PublicDashboardsEnabled = publicDashboards . Key ( "enabled" ) . MustBool ( true )
}
2024-09-23 09:50:11 -05:00
func ( cfg * Cfg ) DefaultOrgID ( ) int64 {
if cfg . AutoAssignOrg && cfg . AutoAssignOrgId > 0 {
return int64 ( cfg . AutoAssignOrgId )
}
return int64 ( 1 )
}