From be46dd1f86afbf58b8cbe987445ab95ef8d6f7ea Mon Sep 17 00:00:00 2001 From: Chris Alexander Date: Wed, 15 Jun 2022 10:12:51 -0500 Subject: [PATCH 1/4] Allow config defaults to be sourced from ENV This allows the default config to source values from ENV variables if they are set. With this change we don't have to modify the docker CMD or edit the config.yml and then relaunch. --- spec/config_spec.cr | 21 +++++++++++++++++++-- src/config.cr | 46 ++++++++++++++++++++++----------------------- src/util/util.cr | 4 ++-- 3 files changed, 44 insertions(+), 27 deletions(-) diff --git a/spec/config_spec.cr b/spec/config_spec.cr index e2d5fca..5ea6f89 100644 --- a/spec/config_spec.cr +++ b/spec/config_spec.cr @@ -1,14 +1,31 @@ require "./spec_helper" describe Config do - it "creates config if it does not exist" do - with_default_config do |_, path| + it "creates default config if it does not exist" do + with_default_config do |config, path| File.exists?(path).should be_true + config.port.should eq 9000 end end it "correctly loads config" do config = Config.load "spec/asset/test-config.yml" config.port.should eq 3000 + config.base_url.should eq "/" + end + + it "correctly reads config defaults from ENV" do + ENV["LOG_LEVEL"] = "debug" + config = Config.load "spec/asset/test-config.yml" + config.log_level.should eq "debug" + config.base_url.should eq "/" + end + + it "correctly handles ENV truthiness" do + ENV["CACHE_ENABLED"] = "false" + config = Config.load "spec/asset/test-config.yml" + config.cache_enabled.should be_false + config.cache_log_enabled.should be_true + config.disable_login.should be_false end end diff --git a/src/config.cr b/src/config.cr index 807a74c..dd0fbda 100644 --- a/src/config.cr +++ b/src/config.cr @@ -4,28 +4,28 @@ class Config include YAML::Serializable @[YAML::Field(ignore: true)] - property path = "" - property host = "0.0.0.0" - property port : Int32 = 9000 - property base_url = "/" - property session_secret = "mango-session-secret" - property library_path = "~/mango/library" - property library_cache_path = "~/mango/library.yml.gz" - property db_path = "~/mango/mango.db" - property queue_db_path = "~/mango/queue.db" - property scan_interval_minutes : Int32 = 5 - property thumbnail_generation_interval_hours : Int32 = 24 - property log_level = "info" - property upload_path = "~/mango/uploads" - property plugin_path = "~/mango/plugins" - property download_timeout_seconds : Int32 = 30 - property cache_enabled = true - property cache_size_mbs = 50 - property cache_log_enabled = true - property disable_login = false - property default_username = "" - property auth_proxy_header_name = "" - property plugin_update_interval_hours : Int32 = 24 + property path : String = "" + property host : String = (ENV["LISTEN_HOST"]? || "0.0.0.0") + property port : Int32 = (ENV["LISTEN_PORT"]? || 9000).to_i + property base_url : String = (ENV["BASE_URL"]? || "/") + property session_secret : String = (ENV["SESSION_SECRET"]? || "mango-session-secret") + property library_path : String = (ENV["LIBRARY_PATH"]? || "~/mango/library") + property library_cache_path : String = (ENV["LIBRARY_CACHE_PATH"]? || "~/mango/library.yml.gz") + property db_path : String = (ENV["DB_PATH"]? || "~/mango/mango.db") + property queue_db_path : String = (ENV["QUEUE_DB_PATH"]? || "~/mango/queue.db") + property scan_interval_minutes : Int32 = (ENV["SCAN_INTERVAL"]? || 5).to_i + property thumbnail_generation_interval_hours : Int32 = (ENV["THUMBNAIL_INTERVAL"]? || 24).to_i + property log_level : String = (ENV["LOG_LEVEL"]? || "info") + property upload_path : String = (ENV["UPLOAD_PATH"]? || "~/mango/uploads") + property plugin_path : String = (ENV["PLUGIN_PATH"]? || "~/mango/plugins") + property download_timeout_seconds : Int32 = (ENV["DOWNLOAD_TIMEOUT"]? || 30).to_i + property cache_enabled : Bool = env_is_true?("CACHE_ENABLED", true) + property cache_size_mbs : Int32 = (ENV["CACHE_SIZE"]? || 50).to_i + property cache_log_enabled : Bool = env_is_true?("CACHE_LOG_ENABLED", true) + property disable_login : Bool = env_is_true?("DISABLE_LOGIN", false) + property default_username : String = (ENV["DEFAULT_USERNAME"]? || "") + property auth_proxy_header_name : String = (ENV["AUTH_PROXY_HEADER"]? || "") + property plugin_update_interval_hours : Int32 = (ENV["PLUGIN_UPDATE_INTERVAL"]? || 24).to_i @@singlet : Config? @@ -38,7 +38,7 @@ class Config end def self.load(path : String?) - path = "~/.config/mango/config.yml" if path.nil? + path = (ENV["CONFIG_PATH"]? || "~/.config/mango/config.yml") if path.nil? cfg_path = File.expand_path path, home: true if File.exists? cfg_path config = self.from_yaml File.read cfg_path diff --git a/src/util/util.cr b/src/util/util.cr index e08bd9d..ed3dc46 100644 --- a/src/util/util.cr +++ b/src/util/util.cr @@ -93,9 +93,9 @@ class String end end -def env_is_true?(key : String) : Bool +def env_is_true?(key : String, default : Bool = false) : Bool val = ENV[key.upcase]? || ENV[key.downcase]? - return false unless val + return default unless val val.downcase.in? "1", "true" end From 2e91028ead3ade765a75ac1fe76781c9a82d596d Mon Sep 17 00:00:00 2001 From: Chris Alexander Date: Wed, 15 Jun 2022 10:12:51 -0500 Subject: [PATCH 2/4] Allow config defaults to be sourced from ENV This allows the default config to source values from ENV variables if they are set. With this change we don't have to modify the docker CMD or edit the config.yml and then relaunch. --- spec/config_spec.cr | 21 ++++++++++++++++-- src/config.cr | 52 +++++++++++++++++++++++++-------------------- src/util/util.cr | 4 ++-- 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/spec/config_spec.cr b/spec/config_spec.cr index e2d5fca..5ea6f89 100644 --- a/spec/config_spec.cr +++ b/spec/config_spec.cr @@ -1,14 +1,31 @@ require "./spec_helper" describe Config do - it "creates config if it does not exist" do - with_default_config do |_, path| + it "creates default config if it does not exist" do + with_default_config do |config, path| File.exists?(path).should be_true + config.port.should eq 9000 end end it "correctly loads config" do config = Config.load "spec/asset/test-config.yml" config.port.should eq 3000 + config.base_url.should eq "/" + end + + it "correctly reads config defaults from ENV" do + ENV["LOG_LEVEL"] = "debug" + config = Config.load "spec/asset/test-config.yml" + config.log_level.should eq "debug" + config.base_url.should eq "/" + end + + it "correctly handles ENV truthiness" do + ENV["CACHE_ENABLED"] = "false" + config = Config.load "spec/asset/test-config.yml" + config.cache_enabled.should be_false + config.cache_log_enabled.should be_true + config.disable_login.should be_false end end diff --git a/src/config.cr b/src/config.cr index 807a74c..f4a2cf3 100644 --- a/src/config.cr +++ b/src/config.cr @@ -4,28 +4,34 @@ class Config include YAML::Serializable @[YAML::Field(ignore: true)] - property path = "" - property host = "0.0.0.0" - property port : Int32 = 9000 - property base_url = "/" - property session_secret = "mango-session-secret" - property library_path = "~/mango/library" - property library_cache_path = "~/mango/library.yml.gz" - property db_path = "~/mango/mango.db" - property queue_db_path = "~/mango/queue.db" - property scan_interval_minutes : Int32 = 5 - property thumbnail_generation_interval_hours : Int32 = 24 - property log_level = "info" - property upload_path = "~/mango/uploads" - property plugin_path = "~/mango/plugins" - property download_timeout_seconds : Int32 = 30 - property cache_enabled = true - property cache_size_mbs = 50 - property cache_log_enabled = true - property disable_login = false - property default_username = "" - property auth_proxy_header_name = "" - property plugin_update_interval_hours : Int32 = 24 + property path : String = "" + property host : String = (ENV["LISTEN_HOST"]? || "0.0.0.0") + property port : Int32 = (ENV["LISTEN_PORT"]? || 9000).to_i + property base_url : String = (ENV["BASE_URL"]? || "/") + property session_secret : String = \ + (ENV["SESSION_SECRET"]? || "mango-session-secret") + property library_path : String = (ENV["LIBRARY_PATH"]? || "~/mango/library") + property library_cache_path : String = \ + (ENV["LIBRARY_CACHE_PATH"]? || "~/mango/library.yml.gz") + property db_path : String = (ENV["DB_PATH"]? || "~/mango/mango.db") + property queue_db_path : String = \ + (ENV["QUEUE_DB_PATH"]? || "~/mango/queue.db") + property scan_interval_minutes : Int32 = (ENV["SCAN_INTERVAL"]? || 5).to_i + property thumbnail_generation_interval_hours : Int32 = \ + (ENV["THUMBNAIL_INTERVAL"]? || 24).to_i + property log_level : String = (ENV["LOG_LEVEL"]? || "info") + property upload_path : String = (ENV["UPLOAD_PATH"]? || "~/mango/uploads") + property plugin_path : String = (ENV["PLUGIN_PATH"]? || "~/mango/plugins") + property download_timeout_seconds : Int32 = \ + (ENV["DOWNLOAD_TIMEOUT"]? || 30).to_i + property cache_enabled : Bool = env_is_true?("CACHE_ENABLED", true) + property cache_size_mbs : Int32 = (ENV["CACHE_SIZE"]? || 50).to_i + property cache_log_enabled : Bool = env_is_true?("CACHE_LOG_ENABLED", true) + property disable_login : Bool = env_is_true?("DISABLE_LOGIN", false) + property default_username : String = (ENV["DEFAULT_USERNAME"]? || "") + property auth_proxy_header_name : String = (ENV["AUTH_PROXY_HEADER"]? || "") + property plugin_update_interval_hours : Int32 = \ + (ENV["PLUGIN_UPDATE_INTERVAL"]? || 24).to_i @@singlet : Config? @@ -38,7 +44,7 @@ class Config end def self.load(path : String?) - path = "~/.config/mango/config.yml" if path.nil? + path = (ENV["CONFIG_PATH"]? || "~/.config/mango/config.yml") if path.nil? cfg_path = File.expand_path path, home: true if File.exists? cfg_path config = self.from_yaml File.read cfg_path diff --git a/src/util/util.cr b/src/util/util.cr index 68e26c7..ce658aa 100644 --- a/src/util/util.cr +++ b/src/util/util.cr @@ -95,9 +95,9 @@ class String end end -def env_is_true?(key : String) : Bool +def env_is_true?(key : String, default : Bool = false) : Bool val = ENV[key.upcase]? || ENV[key.downcase]? - return false unless val + return default unless val val.downcase.in? "1", "true" end From f3eb62a2714326489b6773c4ea0a0be07a767dbe Mon Sep 17 00:00:00 2001 From: Chris Alexander Date: Mon, 27 Jun 2022 09:19:12 -0500 Subject: [PATCH 3/4] Disable line length warnings --- src/config.cr | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/config.cr b/src/config.cr index f4a2cf3..867b8ab 100644 --- a/src/config.cr +++ b/src/config.cr @@ -8,30 +8,30 @@ class Config property host : String = (ENV["LISTEN_HOST"]? || "0.0.0.0") property port : Int32 = (ENV["LISTEN_PORT"]? || 9000).to_i property base_url : String = (ENV["BASE_URL"]? || "/") - property session_secret : String = \ - (ENV["SESSION_SECRET"]? || "mango-session-secret") + # ameba:disable Layout/LineLength + property session_secret : String = (ENV["SESSION_SECRET"]? || "mango-session-secret") property library_path : String = (ENV["LIBRARY_PATH"]? || "~/mango/library") - property library_cache_path : String = \ - (ENV["LIBRARY_CACHE_PATH"]? || "~/mango/library.yml.gz") + # ameba:disable Layout/LineLength + property library_cache_path : String = (ENV["LIBRARY_CACHE_PATH"]? || "~/mango/library.yml.gz") property db_path : String = (ENV["DB_PATH"]? || "~/mango/mango.db") - property queue_db_path : String = \ - (ENV["QUEUE_DB_PATH"]? || "~/mango/queue.db") + # ameba:disable Layout/LineLength + property queue_db_path : String = (ENV["QUEUE_DB_PATH"]? || "~/mango/queue.db") property scan_interval_minutes : Int32 = (ENV["SCAN_INTERVAL"]? || 5).to_i - property thumbnail_generation_interval_hours : Int32 = \ - (ENV["THUMBNAIL_INTERVAL"]? || 24).to_i + # ameba:disable Layout/LineLength + property thumbnail_generation_interval_hours : Int32 = (ENV["THUMBNAIL_INTERVAL"]? || 24).to_i property log_level : String = (ENV["LOG_LEVEL"]? || "info") property upload_path : String = (ENV["UPLOAD_PATH"]? || "~/mango/uploads") property plugin_path : String = (ENV["PLUGIN_PATH"]? || "~/mango/plugins") - property download_timeout_seconds : Int32 = \ - (ENV["DOWNLOAD_TIMEOUT"]? || 30).to_i + # ameba:disable Layout/LineLength + property download_timeout_seconds : Int32 = (ENV["DOWNLOAD_TIMEOUT"]? || 30).to_i property cache_enabled : Bool = env_is_true?("CACHE_ENABLED", true) property cache_size_mbs : Int32 = (ENV["CACHE_SIZE"]? || 50).to_i property cache_log_enabled : Bool = env_is_true?("CACHE_LOG_ENABLED", true) property disable_login : Bool = env_is_true?("DISABLE_LOGIN", false) property default_username : String = (ENV["DEFAULT_USERNAME"]? || "") property auth_proxy_header_name : String = (ENV["AUTH_PROXY_HEADER"]? || "") - property plugin_update_interval_hours : Int32 = \ - (ENV["PLUGIN_UPDATE_INTERVAL"]? || 24).to_i + # ameba:disable Layout/LineLength + property plugin_update_interval_hours : Int32 = (ENV["PLUGIN_UPDATE_INTERVAL"]? || 24).to_i @@singlet : Config? From f2d6d28a72cbc0c932623a643ec541ba14e94287 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Sun, 3 Jul 2022 07:24:33 +0000 Subject: [PATCH 4/4] Define properties with macro --- src/config.cr | 68 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/src/config.cr b/src/config.cr index 867b8ab..2384431 100644 --- a/src/config.cr +++ b/src/config.cr @@ -1,37 +1,51 @@ require "yaml" class Config + private OPTIONS = { + "host" => "0.0.0.0", + "port" => 9000, + "base_url" => "/", + "session_secret" => "mango-session-secret", + "library_path" => "~/mango/library", + "library_cache_path" => "~/mango/library.yml.gz", + "db_path" => "~/mango.db", + "queue_db_path" => "~/mango/queue.db", + "scan_interval_minutes" => 5, + "thumbnail_generation_interval_hours" => 24, + "log_level" => "info", + "upload_path" => "~/mango/uploads", + "plugin_path" => "~/mango/plugins", + "download_timeout_seconds" => 30, + "cache_enabled" => true, + "cache_size_mbs" => 50, + "cache_log_enabled" => true, + "disable_login" => false, + "default_username" => "", + "auth_proxy_header_name" => "", + "plugin_update_interval_hours" => 24, + } + include YAML::Serializable @[YAML::Field(ignore: true)] property path : String = "" - property host : String = (ENV["LISTEN_HOST"]? || "0.0.0.0") - property port : Int32 = (ENV["LISTEN_PORT"]? || 9000).to_i - property base_url : String = (ENV["BASE_URL"]? || "/") - # ameba:disable Layout/LineLength - property session_secret : String = (ENV["SESSION_SECRET"]? || "mango-session-secret") - property library_path : String = (ENV["LIBRARY_PATH"]? || "~/mango/library") - # ameba:disable Layout/LineLength - property library_cache_path : String = (ENV["LIBRARY_CACHE_PATH"]? || "~/mango/library.yml.gz") - property db_path : String = (ENV["DB_PATH"]? || "~/mango/mango.db") - # ameba:disable Layout/LineLength - property queue_db_path : String = (ENV["QUEUE_DB_PATH"]? || "~/mango/queue.db") - property scan_interval_minutes : Int32 = (ENV["SCAN_INTERVAL"]? || 5).to_i - # ameba:disable Layout/LineLength - property thumbnail_generation_interval_hours : Int32 = (ENV["THUMBNAIL_INTERVAL"]? || 24).to_i - property log_level : String = (ENV["LOG_LEVEL"]? || "info") - property upload_path : String = (ENV["UPLOAD_PATH"]? || "~/mango/uploads") - property plugin_path : String = (ENV["PLUGIN_PATH"]? || "~/mango/plugins") - # ameba:disable Layout/LineLength - property download_timeout_seconds : Int32 = (ENV["DOWNLOAD_TIMEOUT"]? || 30).to_i - property cache_enabled : Bool = env_is_true?("CACHE_ENABLED", true) - property cache_size_mbs : Int32 = (ENV["CACHE_SIZE"]? || 50).to_i - property cache_log_enabled : Bool = env_is_true?("CACHE_LOG_ENABLED", true) - property disable_login : Bool = env_is_true?("DISABLE_LOGIN", false) - property default_username : String = (ENV["DEFAULT_USERNAME"]? || "") - property auth_proxy_header_name : String = (ENV["AUTH_PROXY_HEADER"]? || "") - # ameba:disable Layout/LineLength - property plugin_update_interval_hours : Int32 = (ENV["PLUGIN_UPDATE_INTERVAL"]? || 24).to_i + + # Go through the options constant above and define them as properties. + # Allow setting the default values through environment variables. + # Overall precedence: config file > environment variable > default value + {% begin %} + {% for k, v in OPTIONS %} + {% if v.is_a? StringLiteral %} + property {{k.id}} : String = ENV[{{k.upcase}}]? || {{ v }} + {% elsif v.is_a? NumberLiteral %} + property {{k.id}} : Int32 = (ENV[{{k.upcase}}]? || {{ v.id }}).to_i + {% elsif v.is_a? BoolLiteral %} + property {{k.id}} : Bool = env_is_true? {{ k.upcase }}, {{ v.id }} + {% else %} + raise "Unknown type in config option: {{ v.class_name.id }}" + {% end %} + {% end %} + {% end %} @@singlet : Config?