diff options
author | Frédéric Guillot <fred@miniflux.net> | 2019-06-01 18:18:09 -0700 |
---|---|---|
committer | fguillot <fred@miniflux.net> | 2019-06-02 06:30:08 -0700 |
commit | 228862fefaa645026caa483ffe9993bf8c00b22e (patch) | |
tree | 2b590dc6cda3e50928a31ce673641357805f75ce /config | |
parent | 04d85b3c63afcf6c9539fc8dc7a91c4e36c2e8fb (diff) |
Refactor config package
- Parse configuration only once during startup time
- Store configuration values in a global variable
Diffstat (limited to 'config')
-rw-r--r-- | config/config.go | 272 | ||||
-rw-r--r-- | config/config_test.go | 618 | ||||
-rw-r--r-- | config/doc.go | 4 | ||||
-rw-r--r-- | config/options.go | 214 | ||||
-rw-r--r-- | config/parser.go | 124 | ||||
-rw-r--r-- | config/parser_test.go | 79 |
6 files changed, 807 insertions, 504 deletions
diff --git a/config/config.go b/config/config.go index 99afaef..53bd708 100644 --- a/config/config.go +++ b/config/config.go @@ -4,271 +4,11 @@ package config // import "miniflux.app/config" -import ( - "net/url" - "os" - "strconv" - "strings" +// Opts contains configuration options after parsing. +var Opts *Options - "miniflux.app/logger" -) - -const ( - defaultBaseURL = "http://localhost" - defaultDatabaseURL = "user=postgres password=postgres dbname=miniflux2 sslmode=disable" - defaultWorkerPoolSize = 5 - defaultPollingFrequency = 60 - defaultBatchSize = 10 - defaultDatabaseMaxConns = 20 - defaultDatabaseMinConns = 1 - defaultArchiveReadDays = 60 - defaultListenAddr = "127.0.0.1:8080" - defaultCertFile = "" - defaultKeyFile = "" - defaultCertDomain = "" - defaultCertCache = "/tmp/cert_cache" - defaultCleanupFrequency = 24 - defaultProxyImages = "http-only" - defaultOAuth2ClientID = "" - defaultOAuth2ClientSecret = "" - defaultOAuth2RedirectURL = "" - defaultOAuth2Provider = "" -) - -// Config manages configuration parameters. -type Config struct { - IsHTTPS bool - baseURL string - rootURL string - basePath string -} - -func (c *Config) parseBaseURL() { - baseURL := os.Getenv("BASE_URL") - if baseURL == "" { - return - } - - if baseURL[len(baseURL)-1:] == "/" { - baseURL = baseURL[:len(baseURL)-1] - } - - u, err := url.Parse(baseURL) - if err != nil { - logger.Error("Invalid BASE_URL: %v", err) - return - } - - scheme := strings.ToLower(u.Scheme) - if scheme != "https" && scheme != "http" { - logger.Error("Invalid BASE_URL: scheme must be http or https") - return - } - - c.baseURL = baseURL - c.basePath = u.Path - - u.Path = "" - c.rootURL = u.String() -} - -// HasDebugMode returns true if debug mode is enabled. -func (c *Config) HasDebugMode() bool { - return getBooleanValue("DEBUG") -} - -// BaseURL returns the application base URL with path. -func (c *Config) BaseURL() string { - return c.baseURL -} - -// RootURL returns the base URL without path. -func (c *Config) RootURL() string { - return c.rootURL -} - -// BasePath returns the application base path according to the base URL. -func (c *Config) BasePath() string { - return c.basePath -} - -// DatabaseURL returns the database URL. -func (c *Config) DatabaseURL() string { - value, exists := os.LookupEnv("DATABASE_URL") - if !exists { - logger.Info("The environment variable DATABASE_URL is not configured (the default value is used instead)") - } - - if value == "" { - value = defaultDatabaseURL - } - - return value -} - -// DatabaseMaxConns returns the maximum number of database connections. -func (c *Config) DatabaseMaxConns() int { - return getIntValue("DATABASE_MAX_CONNS", defaultDatabaseMaxConns) -} - -// DatabaseMinConns returns the minimum number of database connections. -func (c *Config) DatabaseMinConns() int { - return getIntValue("DATABASE_MIN_CONNS", defaultDatabaseMinConns) -} - -// ListenAddr returns the listen address for the HTTP server. -func (c *Config) ListenAddr() string { - if port := os.Getenv("PORT"); port != "" { - return ":" + port - } - - return getStringValue("LISTEN_ADDR", defaultListenAddr) -} - -// CertFile returns the SSL certificate filename if any. -func (c *Config) CertFile() string { - return getStringValue("CERT_FILE", defaultCertFile) -} - -// KeyFile returns the private key filename for custom SSL certificate. -func (c *Config) KeyFile() string { - return getStringValue("KEY_FILE", defaultKeyFile) -} - -// CertDomain returns the domain to use for Let's Encrypt certificate. -func (c *Config) CertDomain() string { - return getStringValue("CERT_DOMAIN", defaultCertDomain) -} - -// CertCache returns the directory to use for Let's Encrypt session cache. -func (c *Config) CertCache() string { - return getStringValue("CERT_CACHE", defaultCertCache) -} - -// CleanupFrequency returns the interval for cleanup jobs. -func (c *Config) CleanupFrequency() int { - return getIntValue("CLEANUP_FREQUENCY", defaultCleanupFrequency) -} - -// WorkerPoolSize returns the number of background worker. -func (c *Config) WorkerPoolSize() int { - return getIntValue("WORKER_POOL_SIZE", defaultWorkerPoolSize) -} - -// PollingFrequency returns the interval to refresh feeds in the background. -func (c *Config) PollingFrequency() int { - return getIntValue("POLLING_FREQUENCY", defaultPollingFrequency) -} - -// BatchSize returns the number of feeds to send for background processing. -func (c *Config) BatchSize() int { - return getIntValue("BATCH_SIZE", defaultBatchSize) -} - -// IsOAuth2UserCreationAllowed returns true if user creation is allowed for OAuth2 users. -func (c *Config) IsOAuth2UserCreationAllowed() bool { - return getBooleanValue("OAUTH2_USER_CREATION") -} - -// OAuth2ClientID returns the OAuth2 Client ID. -func (c *Config) OAuth2ClientID() string { - return getStringValue("OAUTH2_CLIENT_ID", defaultOAuth2ClientID) -} - -// OAuth2ClientSecret returns the OAuth2 client secret. -func (c *Config) OAuth2ClientSecret() string { - return getStringValue("OAUTH2_CLIENT_SECRET", defaultOAuth2ClientSecret) -} - -// OAuth2RedirectURL returns the OAuth2 redirect URL. -func (c *Config) OAuth2RedirectURL() string { - return getStringValue("OAUTH2_REDIRECT_URL", defaultOAuth2RedirectURL) -} - -// OAuth2Provider returns the name of the OAuth2 provider configured. -func (c *Config) OAuth2Provider() string { - return getStringValue("OAUTH2_PROVIDER", defaultOAuth2Provider) -} - -// HasHSTS returns true if HTTP Strict Transport Security is enabled. -func (c *Config) HasHSTS() bool { - return !getBooleanValue("DISABLE_HSTS") -} - -// RunMigrations returns true if the environment variable RUN_MIGRATIONS is not empty. -func (c *Config) RunMigrations() bool { - return getBooleanValue("RUN_MIGRATIONS") -} - -// CreateAdmin returns true if the environment variable CREATE_ADMIN is not empty. -func (c *Config) CreateAdmin() bool { - return getBooleanValue("CREATE_ADMIN") -} - -// PocketConsumerKey returns the Pocket Consumer Key if defined as environment variable. -func (c *Config) PocketConsumerKey(defaultValue string) string { - return getStringValue("POCKET_CONSUMER_KEY", defaultValue) -} - -// ProxyImages returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy. -func (c *Config) ProxyImages() string { - return getStringValue("PROXY_IMAGES", defaultProxyImages) -} - -// HasHTTPService returns true if the HTTP service is enabled. -func (c *Config) HasHTTPService() bool { - return !getBooleanValue("DISABLE_HTTP_SERVICE") -} - -// HasSchedulerService returns true if the scheduler service is enabled. -func (c *Config) HasSchedulerService() bool { - return !getBooleanValue("DISABLE_SCHEDULER_SERVICE") -} - -// ArchiveReadDays returns the number of days after which marking read items as removed. -func (c *Config) ArchiveReadDays() int { - return getIntValue("ARCHIVE_READ_DAYS", defaultArchiveReadDays) -} - -// NewConfig returns a new Config. -func NewConfig() *Config { - cfg := &Config{ - baseURL: defaultBaseURL, - rootURL: defaultBaseURL, - IsHTTPS: getBooleanValue("HTTPS"), - } - - cfg.parseBaseURL() - return cfg -} - -func getBooleanValue(key string) bool { - value := strings.ToLower(os.Getenv(key)) - if value == "1" || value == "yes" || value == "true" || value == "on" { - return true - } - return false -} - -func getStringValue(key, fallback string) string { - value := os.Getenv(key) - if value == "" { - return fallback - } - - return value -} - -func getIntValue(key string, fallback int) int { - value := os.Getenv(key) - if value == "" { - return fallback - } - - v, err := strconv.Atoi(value) - if err != nil { - return fallback - } - - return v +// ParseConfig parses configuration options. +func ParseConfig() (err error) { + Opts, err = parse() + return err } diff --git a/config/config_test.go b/config/config_test.go index dc6112e..0e691fc 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 Frédéric Guillot. All rights reserved. +// Copyright 2019 Frédéric Guillot. All rights reserved. // Use of this source code is governed by the Apache 2.0 // license that can be found in the LICENSE file. @@ -9,198 +9,125 @@ import ( "testing" ) -func TestGetBooleanValueWithUnsetVariable(t *testing.T) { - os.Clearenv() - if getBooleanValue("MY_TEST_VARIABLE") { - t.Errorf(`Unset variables should returns false`) - } -} - -func TestGetBooleanValue(t *testing.T) { - scenarios := map[string]bool{ - "": false, - "1": true, - "Yes": true, - "yes": true, - "True": true, - "true": true, - "on": true, - "false": false, - "off": false, - "invalid": false, - } - - for input, expected := range scenarios { - os.Clearenv() - os.Setenv("MY_TEST_VARIABLE", input) - result := getBooleanValue("MY_TEST_VARIABLE") - if result != expected { - t.Errorf(`Unexpected result for %q, got %v instead of %v`, input, result, expected) - } - } -} - -func TestGetStringValueWithUnsetVariable(t *testing.T) { - os.Clearenv() - if getStringValue("MY_TEST_VARIABLE", "defaultValue") != "defaultValue" { - t.Errorf(`Unset variables should returns the default value`) - } -} - -func TestGetStringValue(t *testing.T) { +func TestDebugModeOn(t *testing.T) { os.Clearenv() - os.Setenv("MY_TEST_VARIABLE", "test") - if getStringValue("MY_TEST_VARIABLE", "defaultValue") != "test" { - t.Errorf(`Defined variables should returns the specified value`) - } -} + os.Setenv("DEBUG", "1") -func TestGetIntValueWithUnsetVariable(t *testing.T) { - os.Clearenv() - if getIntValue("MY_TEST_VARIABLE", 42) != 42 { - t.Errorf(`Unset variables should returns the default value`) + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) } -} -func TestGetIntValueWithInvalidInput(t *testing.T) { - os.Clearenv() - os.Setenv("MY_TEST_VARIABLE", "invalid integer") - if getIntValue("MY_TEST_VARIABLE", 42) != 42 { - t.Errorf(`Invalid integer should returns the default value`) + if !opts.HasDebugMode() { + t.Fatalf(`Unexpected debug mode value, got "%v"`, opts.HasDebugMode()) } } -func TestGetIntValue(t *testing.T) { - os.Clearenv() - os.Setenv("MY_TEST_VARIABLE", "2018") - if getIntValue("MY_TEST_VARIABLE", 42) != 2018 { - t.Errorf(`Defined variables should returns the specified value`) - } -} - -func TestDebugModeOn(t *testing.T) { +func TestDebugModeOff(t *testing.T) { os.Clearenv() - os.Setenv("DEBUG", "1") - cfg := NewConfig() - if !cfg.HasDebugMode() { - t.Fatalf(`Unexpected debug mode value, got "%v"`, cfg.HasDebugMode()) + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) } -} - -func TestDebugModeOff(t *testing.T) { - os.Clearenv() - cfg := NewConfig() - if cfg.HasDebugMode() { - t.Fatalf(`Unexpected debug mode value, got "%v"`, cfg.HasDebugMode()) + if opts.HasDebugMode() { + t.Fatalf(`Unexpected debug mode value, got "%v"`, opts.HasDebugMode()) } } func TestCustomBaseURL(t *testing.T) { os.Clearenv() os.Setenv("BASE_URL", "http://example.org") - cfg := NewConfig() - if cfg.BaseURL() != "http://example.org" { - t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL()) + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) } - if cfg.RootURL() != "http://example.org" { - t.Fatalf(`Unexpected root URL, got "%s"`, cfg.RootURL()) + if opts.BaseURL() != "http://example.org" { + t.Fatalf(`Unexpected base URL, got "%s"`, opts.BaseURL()) } - if cfg.BasePath() != "" { - t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath()) + if opts.RootURL() != "http://example.org" { + t.Fatalf(`Unexpected root URL, got "%s"`, opts.RootURL()) + } + + if opts.BasePath() != "" { + t.Fatalf(`Unexpected base path, got "%s"`, opts.BasePath()) } } func TestCustomBaseURLWithTrailingSlash(t *testing.T) { os.Clearenv() os.Setenv("BASE_URL", "http://example.org/folder/") - cfg := NewConfig() - if cfg.BaseURL() != "http://example.org/folder" { - t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL()) + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) } - if cfg.RootURL() != "http://example.org" { - t.Fatalf(`Unexpected root URL, got "%s"`, cfg.RootURL()) + if opts.BaseURL() != "http://example.org/folder" { + t.Fatalf(`Unexpected base URL, got "%s"`, opts.BaseURL()) } - if cfg.BasePath() != "/folder" { - t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath()) + if opts.RootURL() != "http://example.org" { + t.Fatalf(`Unexpected root URL, got "%s"`, opts.RootURL()) + } + + if opts.BasePath() != "/folder" { + t.Fatalf(`Unexpected base path, got "%s"`, opts.BasePath()) } } func TestBaseURLWithoutScheme(t *testing.T) { os.Clearenv() os.Setenv("BASE_URL", "example.org/folder/") - cfg := NewConfig() - - if cfg.BaseURL() != "http://localhost" { - t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL()) - } - - if cfg.RootURL() != "http://localhost" { - t.Fatalf(`Unexpected root URL, got "%s"`, cfg.RootURL()) - } - if cfg.BasePath() != "" { - t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath()) + _, err := parse() + if err == nil { + t.Fatalf(`Parsing must fail`) } } func TestBaseURLWithInvalidScheme(t *testing.T) { os.Clearenv() os.Setenv("BASE_URL", "ftp://example.org/folder/") - cfg := NewConfig() - - if cfg.BaseURL() != "http://localhost" { - t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL()) - } - if cfg.RootURL() != "http://localhost" { - t.Fatalf(`Unexpected root URL, got "%s"`, cfg.RootURL()) - } - - if cfg.BasePath() != "" { - t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath()) + _, err := parse() + if err == nil { + t.Fatalf(`Parsing must fail`) } } func TestInvalidBaseURL(t *testing.T) { os.Clearenv() os.Setenv("BASE_URL", "http://example|org") - cfg := NewConfig() - - if cfg.BaseURL() != defaultBaseURL { - t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL()) - } - - if cfg.RootURL() != defaultBaseURL { - t.Fatalf(`Unexpected root URL, got "%s"`, cfg.RootURL()) - } - if cfg.BasePath() != "" { - t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath()) + _, err := parse() + if err == nil { + t.Fatalf(`Parsing must fail`) } } func TestDefaultBaseURL(t *testing.T) { os.Clearenv() - cfg := NewConfig() - if cfg.BaseURL() != defaultBaseURL { - t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL()) + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) } - if cfg.RootURL() != defaultBaseURL { - t.Fatalf(`Unexpected root URL, got "%s"`, cfg.RootURL()) + if opts.BaseURL() != defaultBaseURL { + t.Fatalf(`Unexpected base URL, got "%s"`, opts.BaseURL()) } - if cfg.BasePath() != "" { - t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath()) + if opts.RootURL() != defaultBaseURL { + t.Fatalf(`Unexpected root URL, got "%s"`, opts.RootURL()) + } + + if opts.BasePath() != "" { + t.Fatalf(`Unexpected base path, got "%s"`, opts.BasePath()) } } @@ -208,9 +135,13 @@ func TestDatabaseURL(t *testing.T) { os.Clearenv() os.Setenv("DATABASE_URL", "foobar") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "foobar" - result := cfg.DatabaseURL() + result := opts.DatabaseURL() if result != expected { t.Fatalf(`Unexpected DATABASE_URL value, got %q instead of %q`, result, expected) @@ -219,9 +150,14 @@ func TestDatabaseURL(t *testing.T) { func TestDefaultDatabaseURLValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.DatabaseURL() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultDatabaseURL + result := opts.DatabaseURL() if result != expected { t.Fatalf(`Unexpected DATABASE_URL value, got %q instead of %q`, result, expected) @@ -231,22 +167,30 @@ func TestDefaultDatabaseURLValue(t *testing.T) { func TestDefaultDatabaseMaxConnsValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultDatabaseMaxConns - result := cfg.DatabaseMaxConns() + result := opts.DatabaseMaxConns() if result != expected { t.Fatalf(`Unexpected DATABASE_MAX_CONNS value, got %v instead of %v`, result, expected) } } -func TestDeatabaseMaxConns(t *testing.T) { +func TestDatabaseMaxConns(t *testing.T) { os.Clearenv() os.Setenv("DATABASE_MAX_CONNS", "42") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := 42 - result := cfg.DatabaseMaxConns() + result := opts.DatabaseMaxConns() if result != expected { t.Fatalf(`Unexpected DATABASE_MAX_CONNS value, got %v instead of %v`, result, expected) @@ -256,9 +200,13 @@ func TestDeatabaseMaxConns(t *testing.T) { func TestDefaultDatabaseMinConnsValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultDatabaseMinConns - result := cfg.DatabaseMinConns() + result := opts.DatabaseMinConns() if result != expected { t.Fatalf(`Unexpected DATABASE_MIN_CONNS value, got %v instead of %v`, result, expected) @@ -269,9 +217,13 @@ func TestDatabaseMinConns(t *testing.T) { os.Clearenv() os.Setenv("DATABASE_MIN_CONNS", "42") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := 42 - result := cfg.DatabaseMinConns() + result := opts.DatabaseMinConns() if result != expected { t.Fatalf(`Unexpected DATABASE_MIN_CONNS value, got %v instead of %v`, result, expected) @@ -282,9 +234,13 @@ func TestListenAddr(t *testing.T) { os.Clearenv() os.Setenv("LISTEN_ADDR", "foobar") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "foobar" - result := cfg.ListenAddr() + result := opts.ListenAddr() if result != expected { t.Fatalf(`Unexpected LISTEN_ADDR value, got %q instead of %q`, result, expected) @@ -296,9 +252,13 @@ func TestListenAddrWithPortDefined(t *testing.T) { os.Setenv("PORT", "3000") os.Setenv("LISTEN_ADDR", "foobar") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := ":3000" - result := cfg.ListenAddr() + result := opts.ListenAddr() if result != expected { t.Fatalf(`Unexpected LISTEN_ADDR value, got %q instead of %q`, result, expected) @@ -307,9 +267,14 @@ func TestListenAddrWithPortDefined(t *testing.T) { func TestDefaultListenAddrValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.ListenAddr() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultListenAddr + result := opts.ListenAddr() if result != expected { t.Fatalf(`Unexpected LISTEN_ADDR value, got %q instead of %q`, result, expected) @@ -320,9 +285,13 @@ func TestCertFile(t *testing.T) { os.Clearenv() os.Setenv("CERT_FILE", "foobar") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "foobar" - result := cfg.CertFile() + result := opts.CertFile() if result != expected { t.Fatalf(`Unexpected CERT_FILE value, got %q instead of %q`, result, expected) @@ -331,9 +300,14 @@ func TestCertFile(t *testing.T) { func TestDefaultCertFileValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.CertFile() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultCertFile + result := opts.CertFile() if result != expected { t.Fatalf(`Unexpected CERT_FILE value, got %q instead of %q`, result, expected) @@ -344,9 +318,13 @@ func TestKeyFile(t *testing.T) { os.Clearenv() os.Setenv("KEY_FILE", "foobar") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "foobar" - result := cfg.KeyFile() + result := opts.CertKeyFile() if result != expected { t.Fatalf(`Unexpected KEY_FILE value, got %q instead of %q`, result, expected) @@ -355,9 +333,14 @@ func TestKeyFile(t *testing.T) { func TestDefaultKeyFileValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.KeyFile() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultKeyFile + result := opts.CertKeyFile() if result != expected { t.Fatalf(`Unexpected KEY_FILE value, got %q instead of %q`, result, expected) @@ -368,9 +351,13 @@ func TestCertDomain(t *testing.T) { os.Clearenv() os.Setenv("CERT_DOMAIN", "example.org") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "example.org" - result := cfg.CertDomain() + result := opts.CertDomain() if result != expected { t.Fatalf(`Unexpected CERT_DOMAIN value, got %q instead of %q`, result, expected) @@ -379,9 +366,14 @@ func TestCertDomain(t *testing.T) { func TestDefaultCertDomainValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.CertDomain() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultCertDomain + result := opts.CertDomain() if result != expected { t.Fatalf(`Unexpected CERT_DOMAIN value, got %q instead of %q`, result, expected) @@ -392,9 +384,13 @@ func TestCertCache(t *testing.T) { os.Clearenv() os.Setenv("CERT_CACHE", "foobar") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "foobar" - result := cfg.CertCache() + result := opts.CertCache() if result != expected { t.Fatalf(`Unexpected CERT_CACHE value, got %q instead of %q`, result, expected) @@ -403,9 +399,14 @@ func TestCertCache(t *testing.T) { func TestDefaultCertCacheValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.CertCache() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultCertCache + result := opts.CertCache() if result != expected { t.Fatalf(`Unexpected CERT_CACHE value, got %q instead of %q`, result, expected) @@ -415,9 +416,13 @@ func TestDefaultCertCacheValue(t *testing.T) { func TestDefaultCleanupFrequencyValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultCleanupFrequency - result := cfg.CleanupFrequency() + result := opts.CleanupFrequency() if result != expected { t.Fatalf(`Unexpected CLEANUP_FREQUENCY value, got %v instead of %v`, result, expected) @@ -428,9 +433,13 @@ func TestCleanupFrequency(t *testing.T) { os.Clearenv() os.Setenv("CLEANUP_FREQUENCY", "42") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := 42 - result := cfg.CleanupFrequency() + result := opts.CleanupFrequency() if result != expected { t.Fatalf(`Unexpected CLEANUP_FREQUENCY value, got %v instead of %v`, result, expected) @@ -440,9 +449,13 @@ func TestCleanupFrequency(t *testing.T) { func TestDefaultWorkerPoolSizeValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultWorkerPoolSize - result := cfg.WorkerPoolSize() + result := opts.WorkerPoolSize() if result != expected { t.Fatalf(`Unexpected WORKER_POOL_SIZE value, got %v instead of %v`, result, expected) @@ -453,9 +466,13 @@ func TestWorkerPoolSize(t *testing.T) { os.Clearenv() os.Setenv("WORKER_POOL_SIZE", "42") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := 42 - result := cfg.WorkerPoolSize() + result := opts.WorkerPoolSize() if result != expected { t.Fatalf(`Unexpected WORKER_POOL_SIZE value, got %v instead of %v`, result, expected) @@ -465,9 +482,13 @@ func TestWorkerPoolSize(t *testing.T) { func TestDefautPollingFrequencyValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultPollingFrequency - result := cfg.PollingFrequency() + result := opts.PollingFrequency() if result != expected { t.Fatalf(`Unexpected POLLING_FREQUENCY value, got %v instead of %v`, result, expected) @@ -478,9 +499,13 @@ func TestPollingFrequency(t *testing.T) { os.Clearenv() os.Setenv("POLLING_FREQUENCY", "42") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := 42 - result := cfg.PollingFrequency() + result := opts.PollingFrequency() if result != expected { t.Fatalf(`Unexpected POLLING_FREQUENCY value, got %v instead of %v`, result, expected) @@ -490,9 +515,13 @@ func TestPollingFrequency(t *testing.T) { func TestDefaultBatchSizeValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultBatchSize - result := cfg.BatchSize() + result := opts.BatchSize() if result != expected { t.Fatalf(`Unexpected BATCH_SIZE value, got %v instead of %v`, result, expected) @@ -503,9 +532,13 @@ func TestBatchSize(t *testing.T) { os.Clearenv() os.Setenv("BATCH_SIZE", "42") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := 42 - result := cfg.BatchSize() + result := opts.BatchSize() if result != expected { t.Fatalf(`Unexpected BATCH_SIZE value, got %v instead of %v`, result, expected) @@ -515,9 +548,13 @@ func TestBatchSize(t *testing.T) { func TestOAuth2UserCreationWhenUnset(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := false - result := cfg.IsOAuth2UserCreationAllowed() + result := opts.IsOAuth2UserCreationAllowed() if result != expected { t.Fatalf(`Unexpected OAUTH2_USER_CREATION value, got %v instead of %v`, result, expected) @@ -528,9 +565,13 @@ func TestOAuth2UserCreationAdmin(t *testing.T) { os.Clearenv() os.Setenv("OAUTH2_USER_CREATION", "1") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := true - result := cfg.IsOAuth2UserCreationAllowed() + result := opts.IsOAuth2UserCreationAllowed() if result != expected { t.Fatalf(`Unexpected OAUTH2_USER_CREATION value, got %v instead of %v`, result, expected) @@ -541,9 +582,13 @@ func TestOAuth2ClientID(t *testing.T) { os.Clearenv() os.Setenv("OAUTH2_CLIENT_ID", "foobar") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "foobar" - result := cfg.OAuth2ClientID() + result := opts.OAuth2ClientID() if result != expected { t.Fatalf(`Unexpected OAUTH2_CLIENT_ID value, got %q instead of %q`, result, expected) @@ -552,9 +597,14 @@ func TestOAuth2ClientID(t *testing.T) { func TestDefaultOAuth2ClientIDValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.OAuth2ClientID() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultOAuth2ClientID + result := opts.OAuth2ClientID() if result != expected { t.Fatalf(`Unexpected OAUTH2_CLIENT_ID value, got %q instead of %q`, result, expected) @@ -565,9 +615,13 @@ func TestOAuth2ClientSecret(t *testing.T) { os.Clearenv() os.Setenv("OAUTH2_CLIENT_SECRET", "secret") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "secret" - result := cfg.OAuth2ClientSecret() + result := opts.OAuth2ClientSecret() if result != expected { t.Fatalf(`Unexpected OAUTH2_CLIENT_SECRET value, got %q instead of %q`, result, expected) @@ -576,9 +630,14 @@ func TestOAuth2ClientSecret(t *testing.T) { func TestDefaultOAuth2ClientSecretValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.OAuth2ClientSecret() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultOAuth2ClientSecret + result := opts.OAuth2ClientSecret() if result != expected { t.Fatalf(`Unexpected OAUTH2_CLIENT_SECRET value, got %q instead of %q`, result, expected) @@ -589,9 +648,13 @@ func TestOAuth2RedirectURL(t *testing.T) { os.Clearenv() os.Setenv("OAUTH2_REDIRECT_URL", "http://example.org") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "http://example.org" - result := cfg.OAuth2RedirectURL() + result := opts.OAuth2RedirectURL() if result != expected { t.Fatalf(`Unexpected OAUTH2_REDIRECT_URL value, got %q instead of %q`, result, expected) @@ -600,9 +663,14 @@ func TestOAuth2RedirectURL(t *testing.T) { func TestDefaultOAuth2RedirectURLValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.OAuth2RedirectURL() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultOAuth2RedirectURL + result := opts.OAuth2RedirectURL() if result != expected { t.Fatalf(`Unexpected OAUTH2_REDIRECT_URL value, got %q instead of %q`, result, expected) @@ -613,9 +681,13 @@ func TestOAuth2Provider(t *testing.T) { os.Clearenv() os.Setenv("OAUTH2_PROVIDER", "google") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "google" - result := cfg.OAuth2Provider() + result := opts.OAuth2Provider() if result != expected { t.Fatalf(`Unexpected OAUTH2_PROVIDER value, got %q instead of %q`, result, expected) @@ -624,9 +696,14 @@ func TestOAuth2Provider(t *testing.T) { func TestDefaultOAuth2ProviderValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.OAuth2Provider() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultOAuth2Provider + result := opts.OAuth2Provider() if result != expected { t.Fatalf(`Unexpected OAUTH2_PROVIDER value, got %q instead of %q`, result, expected) @@ -636,9 +713,13 @@ func TestDefaultOAuth2ProviderValue(t *testing.T) { func TestHSTSWhenUnset(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := true - result := cfg.HasHSTS() + result := opts.HasHSTS() if result != expected { t.Fatalf(`Unexpected DISABLE_HSTS value, got %v instead of %v`, result, expected) @@ -649,9 +730,13 @@ func TestHSTS(t *testing.T) { os.Clearenv() os.Setenv("DISABLE_HSTS", "1") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := false - result := cfg.HasHSTS() + result := opts.HasHSTS() if result != expected { t.Fatalf(`Unexpected DISABLE_HSTS value, got %v instead of %v`, result, expected) @@ -661,9 +746,13 @@ func TestHSTS(t *testing.T) { func TestDisableHTTPServiceWhenUnset(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := true - result := cfg.HasHTTPService() + result := opts.HasHTTPService() if result != expected { t.Fatalf(`Unexpected DISABLE_HTTP_SERVICE value, got %v instead of %v`, result, expected) @@ -674,9 +763,13 @@ func TestDisableHTTPService(t *testing.T) { os.Clearenv() os.Setenv("DISABLE_HTTP_SERVICE", "1") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := false - result := cfg.HasHTTPService() + result := opts.HasHTTPService() if result != expected { t.Fatalf(`Unexpected DISABLE_HTTP_SERVICE value, got %v instead of %v`, result, expected) @@ -686,9 +779,13 @@ func TestDisableHTTPService(t *testing.T) { func TestDisableSchedulerServiceWhenUnset(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := true - result := cfg.HasSchedulerService() + result := opts.HasSchedulerService() if result != expected { t.Fatalf(`Unexpected DISABLE_SCHEDULER_SERVICE value, got %v instead of %v`, result, expected) @@ -699,9 +796,13 @@ func TestDisableSchedulerService(t *testing.T) { os.Clearenv() os.Setenv("DISABLE_SCHEDULER_SERVICE", "1") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := false - result := cfg.HasSchedulerService() + result := opts.HasSchedulerService() if result != expected { t.Fatalf(`Unexpected DISABLE_SCHEDULER_SERVICE value, got %v instead of %v`, result, expected) @@ -712,9 +813,13 @@ func TestArchiveReadDays(t *testing.T) { os.Clearenv() os.Setenv("ARCHIVE_READ_DAYS", "7") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := 7 - result := cfg.ArchiveReadDays() + result := opts.ArchiveReadDays() if result != expected { t.Fatalf(`Unexpected ARCHIVE_READ_DAYS value, got %v instead of %v`, result, expected) @@ -724,9 +829,13 @@ func TestArchiveReadDays(t *testing.T) { func TestRunMigrationsWhenUnset(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := false - result := cfg.RunMigrations() + result := opts.RunMigrations() if result != expected { t.Fatalf(`Unexpected RUN_MIGRATIONS value, got %v instead of %v`, result, expected) @@ -737,9 +846,13 @@ func TestRunMigrations(t *testing.T) { os.Clearenv() os.Setenv("RUN_MIGRATIONS", "yes") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := true - result := cfg.RunMigrations() + result := opts.RunMigrations() if result != expected { t.Fatalf(`Unexpected RUN_MIGRATIONS value, got %v instead of %v`, result, expected) @@ -749,9 +862,13 @@ func TestRunMigrations(t *testing.T) { func TestCreateAdminWhenUnset(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := false - result := cfg.CreateAdmin() + result := opts.CreateAdmin() if result != expected { t.Fatalf(`Unexpected CREATE_ADMIN value, got %v instead of %v`, result, expected) @@ -762,9 +879,13 @@ func TestCreateAdmin(t *testing.T) { os.Clearenv() os.Setenv("CREATE_ADMIN", "true") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := true - result := cfg.CreateAdmin() + result := opts.CreateAdmin() if result != expected { t.Fatalf(`Unexpected CREATE_ADMIN value, got %v instead of %v`, result, expected) @@ -775,9 +896,13 @@ func TestPocketConsumerKeyFromEnvVariable(t *testing.T) { os.Clearenv() os.Setenv("POCKET_CONSUMER_KEY", "something") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "something" - result := cfg.PocketConsumerKey("default") + result := opts.PocketConsumerKey("default") if result != expected { t.Fatalf(`Unexpected POCKET_CONSUMER_KEY value, got %q instead of %q`, result, expected) @@ -787,9 +912,13 @@ func TestPocketConsumerKeyFromEnvVariable(t *testing.T) { func TestPocketConsumerKeyFromUserPrefs(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "default" - result := cfg.PocketConsumerKey("default") + result := opts.PocketConsumerKey("default") if result != expected { t.Fatalf(`Unexpected POCKET_CONSUMER_KEY value, got %q instead of %q`, result, expected) @@ -800,9 +929,13 @@ func TestProxyImages(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "all") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "all" - result := cfg.ProxyImages() + result := opts.ProxyImages() if result != expected { t.Fatalf(`Unexpected PROXY_IMAGES value, got %q instead of %q`, result, expected) @@ -811,9 +944,14 @@ func TestProxyImages(t *testing.T) { func TestDefaultProxyImagesValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.ProxyImages() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultProxyImages + result := opts.ProxyImages() if result != expected { t.Fatalf(`Unexpected PROXY_IMAGES value, got %q instead of %q`, result, expected) @@ -822,19 +960,27 @@ func TestDefaultProxyImagesValue(t *testing.T) { func TestHTTPSOff(t *testing.T) { os.Clearenv() - cfg := NewConfig() - if cfg.IsHTTPS { - t.Fatalf(`Unexpected HTTPS value, got "%v"`, cfg.IsHTTPS) + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + + if opts.HTTPS { + t.Fatalf(`Unexpected HTTPS value, got "%v"`, opts.HTTPS) } } func TestHTTPSOn(t *testing.T) { os.Clearenv() os.Setenv("HTTPS", "on") - cfg := NewConfig() - if !cfg.IsHTTPS { - t.Fatalf(`Unexpected HTTPS value, got "%v"`, cfg.IsHTTPS) + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + + if !opts.HTTPS { + t.Fatalf(`Unexpected HTTPS value, got "%v"`, opts.HTTPS) } } diff --git a/config/doc.go b/config/doc.go index 4b4814b..c8121ac 100644 --- a/config/doc.go +++ b/config/doc.go @@ -1,10 +1,10 @@ -// Copyright 2018 Frédéric Guillot. All rights reserved. +// Copyright 2019 Frédéric Guillot. All rights reserved. // Use of this source code is governed by the Apache 2.0 // license that can be found in the LICENSE file. /* -Package config handles configuration values for the application. +Package config handles configuration management for the application. */ package config // import "miniflux.app/config" diff --git a/config/options.go b/config/options.go new file mode 100644 index 0000000..ad8d2c7 --- /dev/null +++ b/config/options.go @@ -0,0 +1,214 @@ +// Copyright 2019 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package config // import "miniflux.app/config" + +const ( + defaultBaseURL = "http://localhost" + defaultWorkerPoolSize = 5 + defaultPollingFrequency = 60 + defaultBatchSize = 10 + defaultDatabaseURL = "user=postgres password=postgres dbname=miniflux2 sslmode=disable" + defaultDatabaseMaxConns = 20 + defaultDatabaseMinConns = 1 + defaultArchiveReadDays = 60 + defaultListenAddr = "127.0.0.1:8080" + defaultCertFile = "" + defaultKeyFile = "" + defaultCertDomain = "" + defaultCertCache = "/tmp/cert_cache" + defaultCleanupFrequency = 24 + defaultProxyImages = "http-only" + defaultOAuth2ClientID = "" + defaultOAuth2ClientSecret = "" + defaultOAuth2RedirectURL = "" + defaultOAuth2Provider = "" +) + +// Options contains configuration options. +type Options struct { + HTTPS bool + hsts bool + httpService bool + schedulerService bool + debug bool + baseURL string + rootURL string + basePath string + databaseURL string + databaseMaxConns int + databaseMinConns int + runMigrations bool + listenAddr string + certFile string + certDomain string + certCache string + certKeyFile string + cleanupFrequency int + archiveReadDays int + pollingFrequency int + batchSize int + workerPoolSize int + createAdmin bool + proxyImages string + oauth2UserCreationAllowed bool + oauth2ClientID string + oauth2ClientSecret string + oauth2RedirectURL string + oauth2Provider string + pocketConsumerKey string +} + +// HasDebugMode returns true if debug mode is enabled. +func (o *Options) HasDebugMode() bool { + return o.debug +} + +// BaseURL returns the application base URL with path. +func (o *Options) BaseURL() string { + return o.baseURL +} + +// RootURL returns the base URL without path. +func (o *Options) RootURL() string { + return o.rootURL +} + +// BasePath returns the application base path according to the base URL. +func (o *Options) BasePath() string { + return o.basePath +} + +// IsDefaultDatabaseURL returns true if the default database URL is used. +func (o *Options) IsDefaultDatabaseURL() bool { + return o.databaseURL == defaultDatabaseURL +} + +// DatabaseURL returns the database URL. +func (o *Options) DatabaseURL() string { + return o.databaseURL +} + +// DatabaseMaxConns returns the maximum number of database connections. +func (o *Options) DatabaseMaxConns() int { + return o.databaseMaxConns +} + +// DatabaseMinConns returns the minimum number of database connections. +func (o *Options) DatabaseMinConns() int { + return o.databaseMinConns +} + +// ListenAddr returns the listen address for the HTTP server. +func (o *Options) ListenAddr() string { + return o.listenAddr +} + +// CertFile returns the SSL certificate filename if any. +func (o *Options) CertFile() string { + return o.certFile +} + +// CertKeyFile returns the private key filename for custom SSL certificate. +func (o *Options) CertKeyFile() string { + return o.certKeyFile +} + +// CertDomain returns the domain to use for Let's Encrypt certificate. +func (o *Options) CertDomain() string { + return o.certDomain +} + +// CertCache returns the directory to use for Let's Encrypt session cache. +func (o *Options) CertCache() string { + return o.certCache +} + +// CleanupFrequency returns the interval for cleanup jobs. +func (o *Options) CleanupFrequency() int { + return o.cleanupFrequency +} + +// WorkerPoolSize returns the number of background worker. +func (o *Options) WorkerPoolSize() int { + return o.workerPoolSize +} + +// PollingFrequency returns the interval to refresh feeds in the background. +func (o *Options) PollingFrequency() int { + return o.pollingFrequency +} + +// BatchSize returns the number of feeds to send for background processing. +func (o *Options) BatchSize() int { + return o.batchSize +} + +// IsOAuth2UserCreationAllowed returns true if user creation is allowed for OAuth2 users. +func (o *Options) IsOAuth2UserCreationAllowed() bool { + return o.oauth2UserCreationAllowed +} + +// OAuth2ClientID returns the OAuth2 Client ID. +func (o *Options) OAuth2ClientID() string { + return o.oauth2ClientID +} + +// OAuth2ClientSecret returns the OAuth2 client secret. +func (o *Options) OAuth2ClientSecret() string { + return o.oauth2ClientSecret +} + +// OAuth2RedirectURL returns the OAuth2 redirect URL. +func (o *Options) OAuth2RedirectURL() string { + return o.oauth2RedirectURL +} + +// OAuth2Provider returns the name of the OAuth2 provider configured. +func (o *Options) OAuth2Provider() string { + return o.oauth2Provider +} + +// HasHSTS returns true if HTTP Strict Transport Security is enabled. +func (o *Options) HasHSTS() bool { + return o.hsts +} + +// RunMigrations returns true if the environment variable RUN_MIGRATIONS is not empty. +func (o *Options) RunMigrations() bool { + return o.runMigrations +} + +// CreateAdmin returns true if the environment variable CREATE_ADMIN is not empty. +func (o *Options) CreateAdmin() bool { + return o.createAdmin +} + +// ProxyImages returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy. +func (o *Options) ProxyImages() string { + return o.proxyImages +} + +// HasHTTPService returns true if the HTTP service is enabled. +func (o *Options) HasHTTPService() bool { + return o.httpService +} + +// HasSchedulerService returns true if the scheduler service is enabled. +func (o *Options) HasSchedulerService() bool { + return o.schedulerService +} + +// ArchiveReadDays returns the number of days after which marking read items as removed. +func (o *Options) ArchiveReadDays() int { + return o.archiveReadDays +} + +// PocketConsumerKey returns the Pocket Consumer Key if configured. +func (o *Options) PocketConsumerKey(defaultValue string) string { + if o.pocketConsumerKey != "" { + return o.pocketConsumerKey + } + return defaultValue +} diff --git a/config/parser.go b/config/parser.go new file mode 100644 index 0000000..996d0a8 --- /dev/null +++ b/config/parser.go @@ -0,0 +1,124 @@ +// Copyright 2019 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package config // import "miniflux.app/config" + +import ( + "errors" + "fmt" + "net/url" + "os" + "strconv" + "strings" +) + +func parse() (opts *Options, err error) { + opts = &Options{} + opts.baseURL, opts.rootURL, opts.basePath, err = parseBaseURL() + if err != nil { + return nil, err + } + + opts.debug = getBooleanValue("DEBUG") + opts.listenAddr = parseListenAddr() + + opts.databaseURL = getStringValue("DATABASE_URL", defaultDatabaseURL) + opts.databaseMaxConns = getIntValue("DATABASE_MAX_CONNS", defaultDatabaseMaxConns) + opts.databaseMinConns = getIntValue("DATABASE_MIN_CONNS", defaultDatabaseMinConns) + opts.runMigrations = getBooleanValue("RUN_MIGRATIONS") + + opts.hsts = !getBooleanValue("DISABLE_HSTS") + opts.HTTPS = getBooleanValue("HTTPS") + + opts.schedulerService = !getBooleanValue("DISABLE_SCHEDULER_SERVICE") + opts.httpService = !getBooleanValue("DISABLE_HTTP_SERVICE") + + opts.certFile = getStringValue("CERT_FILE", defaultCertFile) + opts.certKeyFile = getStringValue("KEY_FILE", defaultKeyFile) + opts.certDomain = getStringValue("CERT_DOMAIN", defaultCertDomain) + opts.certCache = getStringValue("CERT_CACHE", defaultCertCache) + + opts.cleanupFrequency = getIntValue("CLEANUP_FREQUENCY", defaultCleanupFrequency) + opts.workerPoolSize = getIntValue("WORKER_POOL_SIZE", defaultWorkerPoolSize) + opts.pollingFrequency = getIntValue("POLLING_FREQUENCY", defaultPollingFrequency) + opts.batchSize = getIntValue("BATCH_SIZE", defaultBatchSize) + opts.archiveReadDays = getIntValue("ARCHIVE_READ_DAYS", defaultArchiveReadDays) + opts.proxyImages = getStringValue("PROXY_IMAGES", defaultProxyImages) + + opts.oauth2UserCreationAllowed = getBooleanValue("OAUTH2_USER_CREATION") + opts.oauth2ClientID = getStringValue("OAUTH2_CLIENT_ID", defaultOAuth2ClientID) + opts.oauth2ClientSecret = getStringValue("OAUTH2_CLIENT_SECRET", defaultOAuth2ClientSecret) + opts.oauth2RedirectURL = getStringValue("OAUTH2_REDIRECT_URL", defaultOAuth2RedirectURL) + opts.oauth2Provider = getStringValue("OAUTH2_PROVIDER", defaultOAuth2Provider) + + opts.pocketConsumerKey = getStringValue("POCKET_CONSUMER_KEY", "") + + opts.createAdmin = getBooleanValue("CREATE_ADMIN") + + return opts, nil +} + +func parseBaseURL() (string, string, string, error) { + baseURL := os.Getenv("BASE_URL") + if baseURL == "" { + return defaultBaseURL, defaultBaseURL, "", nil + } + + if baseURL[len(baseURL)-1:] == "/" { + baseURL = baseURL[:len(baseURL)-1] + } + + u, err := url.Parse(baseURL) + if err != nil { + return "", "", "", fmt.Errorf("Invalid BASE_URL: %v", err) + } + + scheme := strings.ToLower(u.Scheme) + if scheme != "https" && scheme != "http" { + return "", "", "", errors.New("Invalid BASE_URL: scheme must be http or https") + } + + basePath := u.Path + u.Path = "" + return baseURL, u.String(), basePath, nil +} + +func parseListenAddr() string { + if port := os.Getenv("PORT"); port != "" { + return ":" + port + } + + return getStringValue("LISTEN_ADDR", defaultListenAddr) +} + +func getBooleanValue(key string) bool { + value := strings.ToLower(os.Getenv(key)) + if value == "1" || value == "yes" || value == "true" || value == "on" { + return true + } + return false +} + +func getStringValue(key, fallback string) string { + value := os.Getenv(key) + if value == "" { + return fallback + } + + return value +} + +func getIntValue(key string, fallback int) int { + value := os.Getenv(key) + if value == "" { + return fallback + } + + v, err := strconv.Atoi(value) + if err != nil { + return fallback + } + + return v +} diff --git a/config/parser_test.go b/config/parser_test.go new file mode 100644 index 0000000..ba454b4 --- /dev/null +++ b/config/parser_test.go @@ -0,0 +1,79 @@ +// Copyright 2019 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package config // import "miniflux.app/config" + +import ( + "os" + "testing" +) + +func TestGetBooleanValueWithUnsetVariable(t *testing.T) { + os.Clearenv() + if getBooleanValue("MY_TEST_VARIABLE") { + t.Errorf(`Unset variables should returns false`) + } +} + +func TestGetBooleanValue(t *testing.T) { + scenarios := map[string]bool{ + "": false, + "1": true, + "Yes": true, + "yes": true, + "True": true, + "true": true, + "on": true, + "false": false, + "off": false, + "invalid": false, + } + + for input, expected := range scenarios { + os.Clearenv() + os.Setenv("MY_TEST_VARIABLE", input) + result := getBooleanValue("MY_TEST_VARIABLE") + if result != expected { + t.Errorf(`Unexpected result for %q, got %v instead of %v`, input, result, expected) + } + } +} + +func TestGetStringValueWithUnsetVariable(t *testing.T) { + os.Clearenv() + if getStringValue("MY_TEST_VARIABLE", "defaultValue") != "defaultValue" { + t.Errorf(`Unset variables should returns the default value`) + } +} + +func TestGetStringValue(t *testing.T) { + os.Clearenv() + os.Setenv("MY_TEST_VARIABLE", "test") + if getStringValue("MY_TEST_VARIABLE", "defaultValue") != "test" { + t.Errorf(`Defined variables should returns the specified value`) + } +} + +func TestGetIntValueWithUnsetVariable(t *testing.T) { + os.Clearenv() + if getIntValue("MY_TEST_VARIABLE", 42) != 42 { + t.Errorf(`Unset variables should returns the default value`) + } +} + +func TestGetIntValueWithInvalidInput(t *testing.T) { + os.Clearenv() + os.Setenv("MY_TEST_VARIABLE", "invalid integer") + if getIntValue("MY_TEST_VARIABLE", 42) != 42 { + t.Errorf(`Invalid integer should returns the default value`) + } +} + +func TestGetIntValue(t *testing.T) { + os.Clearenv() + os.Setenv("MY_TEST_VARIABLE", "2018") + if getIntValue("MY_TEST_VARIABLE", 42) != 2018 { + t.Errorf(`Defined variables should returns the specified value`) + } +} |