diff --git a/lib/pact_broker/config/runtime_configuration.rb b/lib/pact_broker/config/runtime_configuration.rb index 86a0a78da..f8c5583aa 100644 --- a/lib/pact_broker/config/runtime_configuration.rb +++ b/lib/pact_broker/config/runtime_configuration.rb @@ -31,6 +31,8 @@ class RuntimeConfiguration < Anyway::Config include RuntimeConfigurationDatabaseMethods include RuntimeConfigurationBasicAuthMethods + config_name :pact_broker + # logging attributes attr_config( log_dir: File.expand_path("./log"), @@ -91,7 +93,7 @@ class RuntimeConfiguration < Anyway::Config allow_dangerous_contract_modification: false, semver_formats: ["%M.%m.%p%s%d", "%M.%m", "%M"], seed_example_data: true, - features: [] + features: {} ) def self.getter_and_setter_method_names @@ -105,8 +107,19 @@ def self.getter_and_setter_method_names config_attributes + config_attributes.collect{ |k| "#{k}=".to_sym } + extra_methods - [:base_url] end - config_name :pact_broker + COERCE_FEATURES = lambda { | value | + if value.is_a?(String) + value.split(" ").each_with_object({}) { | k, h | h[k.downcase.to_sym] = true } + elsif value.is_a?(Array) + value.each_with_object({}) { | k, h | h[k.downcase.to_sym] = true } + elsif value.is_a?(Hash) + value.each_with_object({}) { | (k, v), new_hash | new_hash[k.downcase.to_sym] = Anyway::AutoCast.call(v) } + else + raise_validation_error("Expected a String, Hash or Array for features but got a #{value.class.name}") + end + } + coerce_types(features: COERCE_FEATURES) sensitive_values(:database_url, :database_password) def log_level= log_level @@ -179,10 +192,6 @@ def main_branch_candidates= main_branch_candidates super(value_to_string_array(main_branch_candidates, "main_branch_candidates")) end - def features= features - super(value_to_string_array(features, "features").collect(&:downcase)) - end - def rack_protection_use= rack_protection_use super(value_to_string_array(rack_protection_use, "rack_protection_use")&.collect(&:to_sym)) end diff --git a/lib/pact_broker/config/runtime_configuration_logging_methods.rb b/lib/pact_broker/config/runtime_configuration_logging_methods.rb index 1b574606a..e53c8db5a 100644 --- a/lib/pact_broker/config/runtime_configuration_logging_methods.rb +++ b/lib/pact_broker/config/runtime_configuration_logging_methods.rb @@ -25,16 +25,19 @@ module InstanceMethods # base_url raises a not implemented error def log_configuration(logger) source_info = to_source_trace - (self.class.config_attributes - [:base_url]).collect(&:to_s).each_with_object({})do | key, new_hash | + attributes_to_log.collect(&:to_s).each_with_object({}) do | key, new_hash | new_hash[key] = { value: self.send(key.to_sym), - source: source_info.dig(key, :source) || {:type=>:defaults} + source: source_info.dig(key, :source) || source_info.dig(key) || { type: :defaults } } end.sort_by { |key, _| key }.each { |key, value| log_config_inner(key, value, logger) } - if self.webhook_redact_sensitive_data == false - logger.warn("WARNING!!! webhook_redact_sensitive_data is set to false. This will allow authentication information to be included in the webhook logs. This should only be used for debugging purposes. Do not run the application permanently in production with this value.") - end + print_warnings + end + + def attributes_to_log + self.class.config_attributes - [:base_url] end + private :attributes_to_log def log_config_inner(key, value, logger) # TODO fix the source display for webhook_certificates set by environment variables @@ -68,6 +71,13 @@ def redact name, value end end private :redact + + def print_warnings + if self.webhook_redact_sensitive_data == false + logger.warn("WARNING!!! webhook_redact_sensitive_data is set to false. This will allow authentication information to be included in the webhook logs. This should only be used for debugging purposes. Do not run the application permanently in production with this value.") + end + end + private :print_warnings end def self.included(receiver) diff --git a/lib/pact_broker/feature_toggle.rb b/lib/pact_broker/feature_toggle.rb index c11fde842..12e989f86 100644 --- a/lib/pact_broker/feature_toggle.rb +++ b/lib/pact_broker/feature_toggle.rb @@ -15,7 +15,7 @@ def self.not_production? end def self.feature_in_env_var?(feature) - PactBroker.configuration.features.include?(feature.to_s.downcase) + PactBroker.configuration.features[feature.to_s.downcase.to_sym] == true end end diff --git a/spec/lib/pact_broker/config/runtime_configuration_spec.rb b/spec/lib/pact_broker/config/runtime_configuration_spec.rb index 0c9ca9304..41dae583e 100644 --- a/spec/lib/pact_broker/config/runtime_configuration_spec.rb +++ b/spec/lib/pact_broker/config/runtime_configuration_spec.rb @@ -1,8 +1,11 @@ require "pact_broker/config/runtime_configuration" +require "anyway/testing/helpers" module PactBroker module Config describe RuntimeConfiguration do + include Anyway::Testing::Helpers + describe "base_url" do it "does not expose base_url for delegation" do expect(RuntimeConfiguration.getter_and_setter_method_names).to_not include :base_url @@ -78,6 +81,54 @@ module Config its(:webhook_certificates) { is_expected.to eq [{ description: "cert1", content: "abc" }, { description: "cert1", content: "abc" }] } end end + + describe "features" do + context "with the PACT_BROKER_FEATURES env var with a space delimited list of enabled features" do + it "parses the string to a hash" do + with_env("PACT_BROKER_FEATURES" => "feat1 feat2") do + expect(RuntimeConfiguration.new.features).to eq feat1: true, feat2: true + end + end + end + + context "with the PACT_BROKER_FEATURES env var with an empty string" do + it "parses the string to a hash" do + with_env("PACT_BROKER_FEATURES" => "") do + expect(RuntimeConfiguration.new.features).to eq({}) + end + end + end + + context "with a different env var for each feature" do + it "merges the env vars into a hash" do + with_env("PACT_BROKER_FEATURES__FEAT1" => "true", "PACT_BROKER_FEATURES__FEAT2" => "false") do + expect(RuntimeConfiguration.new.features).to eq feat1: true, feat2: false + end + end + end + + context "with the list env var defined first and the individual env vars defined last" do + it "uses the individual env vars" do + with_env("PACT_BROKER_FEATURES" => "feat1 feat2 feat3", "PACT_BROKER_FEATURES__FEAT4" => "true", "PACT_BROKER_FEATURES__FEAT5" => "false") do + expect(RuntimeConfiguration.new.features).to eq feat4: true, feat5: false + end + end + end + + context "with the individual env vars defined first and the list env var defined last" do + it "uses the list env var" do + with_env("PACT_BROKER_FEATURES__FEAT4" => "true", "PACT_BROKER_FEATURES__FEAT5" => "false", "PACT_BROKER_FEATURES" => "feat1 feat2 feat3") do + expect(RuntimeConfiguration.new.features).to eq feat1: true, feat2: true, feat3: true + end + end + end + + context "with no feature env vars" do + it "returns an empty hash" do + expect(RuntimeConfiguration.new.features).to eq({}) + end + end + end end end end