Skip to content

Commit

Permalink
feat: support setting feature toggles via individual environment vari…
Browse files Browse the repository at this point in the history
…ables (#609)

PACT-1033
  • Loading branch information
bethesque authored May 25, 2023
1 parent b5d1c93 commit be7d9d5
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 12 deletions.
21 changes: 15 additions & 6 deletions lib/pact_broker/config/runtime_configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
20 changes: 15 additions & 5 deletions lib/pact_broker/config/runtime_configuration_logging_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion lib/pact_broker/feature_toggle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
51 changes: 51 additions & 0 deletions spec/lib/pact_broker/config/runtime_configuration_spec.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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

0 comments on commit be7d9d5

Please sign in to comment.