Skip to content

Commit bb2dbcd

Browse files
authored
support enabling webvalve in non-local, production-like envs (#34)
this changeset makes it so that webvalve has the following behavior: when the env is test/development * webvalve is enabled and always runs in intercepting mode where services are intercepted by default when the env is NOT test/development, e.g. production * webvalve is disabled unless WEBVALVE_ENABLED=1/t/true * when WEBVALVE_ENABLED is truthy, webvalve is enabled in allowing mode where all traffic is allowed by default * $SERVICE_ENABLED=0/f/false can be used to switch a service into intercepting mode, i.e. enable the fake service * when WEBVALVE_ENABLED is truthy and WEBVALVE_SERVICE_ENABLED_DEFAULT=0/f/false then webvalve is enabled in intercepting mode where all traffic is intercepted by default * $SERVICE_ENABLED=1/t/true can be used to switch a service into allowing mode, i.e. allow the traffic to that service to go through to the internet * update the ADR * update changelog * update readme * delete notes
1 parent 8c439b2 commit bb2dbcd

9 files changed

+773
-166
lines changed

CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ and this project aims to adhere to [Semantic Versioning](http://semver.org/spec/
99
### Added
1010
### Removed
1111

12+
## [0.11.0] - 2019-09-23
13+
### Changed
14+
- rework configuration so that WebValve has 3 operating modes: off,
15+
on+allowing, and on+intercepting. support toggling the latter two
16+
modes with
17+
`WEBVALVED_ENABLED=1`+`WEBVALVE_SERVICE_ENABLED_DEFAULT=1` and
18+
`WEBVALVED_ENABLED=1`+`WEBVALVE_SERVICE_ENABLED_DEFAULT=0`.
19+
(https://github.com/Betterment/webvalve/pull/34)
20+
1221
## [0.10.0] - 2019-09-23
1322
### Changed
1423
- `Webvalve.register` no longer accepts classes; you must provide class names as strings. Fixes a Rails 6 deprecation warning. (https://github.com/Betterment/webvalve/pull/35)

README.md

+13-3
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,22 @@ not registered.
227227

228228
## Frequently Asked Questions
229229

230-
> Can I use WebValve in environments like staging and demo?
230+
> Can I use WebValve in environments like staging, demo, and production?
231231
232232
Yes! By default WebValve is only enabled in test and development
233233
environments; however, it can be enabled in other environments by
234-
setting `WEBVALVE_ENABLED=true`. This can be useful for spinning up
235-
cheap, one-off environments for user-testing or demos.
234+
setting `WEBVALVE_ENABLED=true` (actually, any of 1/t/true will work).
235+
This can be useful for spinning up cheap, one-off environments for
236+
user-testing or demos. When WebValve is enabled in any environment other
237+
than development/test it will default services to enabled rather than
238+
disabled, allowing all traffic to pass-thru. This ensures that
239+
production-like environments are run integrated by default. You can
240+
change this behavior by setting `WEBVALVE_SERVICE_ENABLED_DEFAULT=false`
241+
(any of 0/f/false will work). This will default to the same experience
242+
as local development, defaulting services to disabled, intercepting all
243+
traffic. In either of these modes, you can use the
244+
`$SERVICE_ENABLED=true/false` to toggle a specific service into the
245+
desired state.
236246

237247
> Can I use WebValve without Rails?
238248

design/adr-001.md

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# ADR 001: Use ENV variable to control behavior in production-like envs
2+
3+
## Status
4+
5+
Accepted
6+
7+
## Context
8+
9+
We would like to be able to use WebValve to fake external service
10+
integrations in production-like environments.
11+
12+
When using WebValve in a production-like environment, we'd like to be
13+
able to support three different use-cases:
14+
* enable all services by default, and don't load WebValve at all. This
15+
is equivalent to WebValve's current production behavior:
16+
zero-overhead, no risk of accidentally loading a fake service
17+
instead of a real one.
18+
* enable all services by default (as if WebValve is disabled) but allow
19+
each service to be flipped into faking mode based on an env
20+
variable. This allows us to support faking a service in a staging
21+
environment where we cannot actually integrate with a real or
22+
sandbox version of the external service while still connecting to
23+
real versions of all other services.
24+
* disable all services by default (as if WebValve is enabled) but allow
25+
each service to be flipped into real mode based on an env variable.
26+
This allows us to spin up cheap one-off environments for
27+
user-testing, proof-of-concepting, etc.
28+
29+
In summary, we need ways to control:
30+
* the activation of WebValve: loading the library, loading
31+
the fakes, configuring WebMock
32+
* the default mode of WebValve: intercepting vs. allowing
33+
(pass-through)
34+
* the explicit enabling / disabling of individual services
35+
36+
We came up with a few approaches to support these use-cases:
37+
38+
*Activate if required*
39+
This will load and activate WebValve when it's required. If you don't
40+
want to activate it, don't require it. The downside to this approach is
41+
it's easy to accidentally load WebValve in the wrong env. Additionally,
42+
in an env where we want to enable all services by default and only
43+
disable select ones, we'd have to define the ${SERVICE}_ENABLED env
44+
variable for all services and update each time we add a new services,
45+
which can be quite annoying.
46+
47+
*Activate based on env variables*
48+
Introduce a new WEBVALVE_SERVICE_ENABLED_DEFAULT env variable that
49+
controls the default service enabled behavior, or the "mode" webvalve
50+
runs in: on and allowing traffic by default, or on and intercepting
51+
traffic by default. If the WEBVALVE_ENABLED variable is unset, don't
52+
activate the lib. If WEBVALVE_ENABLED is set to truthy (1/t/true) and
53+
WEBVALVE_SERVICE_ENABLED_DEFAULT is unset then load in passthru mode,
54+
allowing fakes to be toggled on explicitly via their ${SERVICE}_ENABLED
55+
env variable. If WEBVALVE_ENABLED is explicitly set to to truthy and
56+
WEBVALVE_SERVICE_ENABLED_DEFAULT is set to falsey (0/f/false) then load
57+
in intercepting mode, allowing fakes to be toggled off explicitly via
58+
their ${SERVICE}_ENABLED env variable.
59+
60+
*Don't support it at all*
61+
Lastly, a sort of non-option option: don't support faking in
62+
production-like envs. Don't support any envs other than dev and test.
63+
Advise that the gem should only go in the dev/test group. This is safest
64+
for production use, but means that production-like envs that want to
65+
swap out real versions of services would have to do it by actually
66+
deploying a version of their fake service and connecting to it out of
67+
process as if it were a real service. This is nice from the "make it
68+
real" angle, but introduces quite a bit of overhead and it's
69+
well-aligned with the WebValve philosophy of making things convenient
70+
and as isolated as possible.
71+
72+
## Decision
73+
74+
We chose the "Activate based on env variables" approach. It introduces
75+
more complexity to this library, but support for these use-cases feels
76+
worth it. We're not the happiest with having
77+
`WEBVALVE_SERVICE_ENABLED_DEFAULT` as an env variable name, but I think
78+
that we can document it clearly to head off confusion for the power
79+
users that actually want to utilize WebValve in production-like
80+
environments. For the standard user, nothing about how they're currently
81+
using WebValve will change.
82+
83+
Here's a summary of the behavior based on environment.
84+
85+
When the env is test/development
86+
* webvalve is enabled and always runs in intercepting mode where
87+
services are intercepted by default
88+
89+
When the env is NOT test/development, e.g. production
90+
* webvalve is disabled unless WEBVALVE_ENABLED=1/t/true
91+
* when WEBVALVE_ENABLED is truthy, webvalve is enabled in allowing mode
92+
where all traffic is allowed by default
93+
* $SERVICE_ENABLED=0/f/false can be used to switch a service into intercepting
94+
mode, i.e. enable the fake service
95+
* when WEBVALVE_ENABLED is truthy and
96+
WEBVALVE_SERVICE_ENABLED_DEFAULT=0/f/false then webvalve is enabled in
97+
intercepting mode where all traffic is intercepted by default
98+
* $SERVICE_ENABLED=1/t/true can be used to switch a service into
99+
allowing mode, i.e. allow the traffic to that service to go through to
100+
the internet
101+
102+
## Consequences
103+
104+
More complexity to manage in this library.
105+
106+
Controlling WebValve activation via an ENV variable, makes it slightly
107+
easier to unintentionally enable WebValve in production.
108+
109+
The current behavior of WEBVALVE_ENABLED in production is slightly
110+
altered: by default we will allow all traffic in production.

lib/webvalve.rb

+3-18
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
require 'active_support/core_ext'
44

55
module WebValve
6-
ALWAYS_ENABLED_ENVS = %w(development test).freeze
7-
ENABLED_VALUES = %w(1 t true).freeze
8-
96
class << self
107
# @!method setup
118
# @see WebValve::Manager#setup
@@ -15,23 +12,11 @@ class << self
1512
# @see WebValve::Manager#allow_url
1613
# @!method reset
1714
# @see WebValve::Manager#reset
18-
delegate :setup, :register, :allow_url, :reset, to: :manager
15+
# @!method enabled?
16+
# @see WebValve::Manager#enabled?
17+
delegate :setup, :register, :allow_url, :reset, :enabled?, to: :manager
1918
attr_writer :logger
2019

21-
def enabled?
22-
if env.in?(ALWAYS_ENABLED_ENVS)
23-
if ENV.key? 'WEBVALVE_ENABLED'
24-
logger.warn(<<~MESSAGE)
25-
WARNING: Ignoring WEBVALVE_ENABLED environment variable setting (#{ENV['WEBVALVE_ENABLED']})
26-
WebValve is always enabled in development and test environments.
27-
MESSAGE
28-
end
29-
true
30-
else
31-
ENABLED_VALUES.include?(ENV['WEBVALVE_ENABLED'])
32-
end
33-
end
34-
3520
def config_paths
3621
@config_paths ||= Set.new
3722
end

lib/webvalve/fake_service_config.rb

+10-7
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ def initialize(service_class_name:, url: nil)
77
@custom_service_url = url
88
end
99

10-
def should_intercept?
11-
WebValve.env.test? || # always intercept in test
12-
(WebValve.enabled? && !service_enabled_in_env?)
10+
def explicitly_enabled?
11+
value_from_env.present? && WebValve::ENABLED_VALUES.include?(value_from_env.to_s)
12+
end
13+
14+
def explicitly_disabled?
15+
value_from_env.present? && WebValve::DISABLED_VALUES.include?(value_from_env.to_s)
1316
end
1417

1518
def service_url
@@ -24,6 +27,10 @@ def service_url
2427

2528
attr_reader :custom_service_url
2629

30+
def value_from_env
31+
ENV["#{service_name.to_s.upcase}_ENABLED"]
32+
end
33+
2734
def missing_url_message
2835
<<~MESSAGE
2936
There is no URL defined for #{service_class_name}.
@@ -36,10 +43,6 @@ def strip_basic_auth(url)
3643
url.to_s.sub(%r(\Ahttp(s)?://[^@/]+@), 'http\1://')
3744
end
3845

39-
def service_enabled_in_env?
40-
WebValve::ENABLED_VALUES.include?(ENV["#{service_name.to_s.upcase}_ENABLED"])
41-
end
42-
4346
def default_service_url
4447
ENV["#{service_name.to_s.upcase}_API_URL"]
4548
end

lib/webvalve/manager.rb

+73-7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
require 'set'
44

55
module WebValve
6+
ALWAYS_ENABLED_ENVS = %w(development test).freeze
7+
ENABLED_VALUES = %w(1 t true).freeze
8+
DISABLED_VALUES = %w(0 f false).freeze
9+
610
# @api private
711
class Manager
812
include Singleton
@@ -19,16 +23,46 @@ def allow_url(url)
1923
end
2024

2125
def setup
22-
fake_service_configs.each do |config|
23-
if config.should_intercept?
24-
webmock_service config
25-
else
26-
allowlist_service config
26+
return unless enabled?
27+
28+
if intercepting?
29+
fake_service_configs.each do |config|
30+
if !WebValve.env.test? && config.explicitly_enabled?
31+
allowlist_service config
32+
else
33+
webmock_service config
34+
end
35+
end
36+
WebMock.disable_net_connect! webmock_disable_options
37+
WebMock.enable!
38+
end
39+
40+
if allowing?
41+
fake_service_configs.each do |config|
42+
if config.explicitly_disabled?
43+
webmock_service config
44+
end
45+
end
46+
if fake_service_configs.any?(&:explicitly_disabled?)
47+
WebMock.allow_net_connect!
48+
WebMock.enable!
2749
end
2850
end
51+
end
52+
53+
# @api private
54+
def enabled?
55+
in_always_intercepting_env? || explicitly_enabled?
56+
end
57+
58+
# @api private
59+
def intercepting?
60+
in_always_intercepting_env? || (explicitly_enabled? && !services_enabled_by_default?)
61+
end
2962

30-
WebMock.enable!
31-
WebMock.disable_net_connect! webmock_disable_options
63+
# @api private
64+
def allowing?
65+
!in_always_intercepting_env? && explicitly_enabled? && services_enabled_by_default?
3266
end
3367

3468
# @api private
@@ -49,6 +83,38 @@ def allowlisted_urls
4983

5084
private
5185

86+
def explicitly_enabled?
87+
ENABLED_VALUES.include?(ENV['WEBVALVE_ENABLED'])
88+
end
89+
90+
def services_enabled_by_default?
91+
if WebValve.env.in?(ALWAYS_ENABLED_ENVS)
92+
if ENV.key? 'WEBVALVE_SERVICE_ENABLED_DEFAULT'
93+
WebValve.logger.warn(<<~MESSAGE)
94+
WARNING: Ignoring WEBVALVE_SERVICE_ENABLED_DEFAULT environment variable setting (#{ENV['WEBVALVE_SERVICE_ENABLED_DEFAULT']})
95+
WebValve is always enabled in intercepting mode in development and test environments.
96+
MESSAGE
97+
end
98+
false
99+
else
100+
ENABLED_VALUES.include?(ENV.fetch('WEBVALVE_SERVICE_ENABLED_DEFAULT', '1'))
101+
end
102+
end
103+
104+
def in_always_intercepting_env?
105+
if WebValve.env.in?(ALWAYS_ENABLED_ENVS)
106+
if ENV.key? 'WEBVALVE_ENABLED'
107+
WebValve.logger.warn(<<~MESSAGE)
108+
WARNING: Ignoring WEBVALVE_ENABLED environment variable setting (#{ENV['WEBVALVE_ENABLED']})
109+
WebValve is always enabled in development and test environments.
110+
MESSAGE
111+
end
112+
true
113+
else
114+
false
115+
end
116+
end
117+
52118
def webmock_disable_options
53119
{ allow_localhost: true }.tap do |opts|
54120
opts[:allow] = allowlisted_url_regexps unless WebValve.env.test?

lib/webvalve/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module WebValve
2-
VERSION = "0.10.0"
2+
VERSION = "0.11.0"
33
end

0 commit comments

Comments
 (0)