guardrail 2.0.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa6a0792d151f43c9a413f2b844681ddcdb8ef04dca8a8b74316bdeff1d3c4a4
4
- data.tar.gz: 24c8495b64f89395fa6922a177e963a4bf815172f1c527e3d2443386e2a42f9a
3
+ metadata.gz: 9b9e58f5329f5eda67ce7018576fc6c2d970df8325c3f60646caf25c2a185fc9
4
+ data.tar.gz: 5450d44f587e98fe84d12a5572f7aa2cde259fc75aebc9d1f28f4c535510af16
5
5
  SHA512:
6
- metadata.gz: c184e8a30569ba0b6121c849238af84ae2da58cbf260d45be5f94b751c7b7ed742cd5a4651daf0a3d32028591cbe35f2980aa81cf7f153ada612e03e4589ad08
7
- data.tar.gz: 1280646bf10326f16566fa1efb1911fa80a26a9236c417845d72bad0f34b17c550975ca2d0d3f6510782b141843b76efd78f55c2a58fc0d9ff32fbb5c9c55bde
6
+ metadata.gz: b1c38cf3c156354856914ebba409fb4e5cb1287109ad66e07da1b0bd831e4dd522487bec8ba1b8bd056fafac7710747ffe691dbdf59a29b14f03999938ec687a
7
+ data.tar.gz: 5a8e7b29e2006a618045c1d17950b17bbaa13fa479589fc93b061055ea6f4ba80a6ccdfa532e75bb9cadc4cdaebd36a2c5b07261d7223b5f235491871e5913bf
data/README.md CHANGED
@@ -3,55 +3,27 @@ GuardRail
3
3
 
4
4
  ## About
5
5
 
6
- GuardRail allows multiple database environments and environment overrides to
7
- ActiveRecord, allowing least-privilege best practices.
6
+ GuardRail is a thin wrapper around Rail 6.1's native role switching.
8
7
 
9
8
  ## Installation
10
9
 
11
- Add `gem 'guardrail'` to your Gemfile (tested with Rails 4.x and 5.x, and also
12
- with the release candidate for Rails 6.0)
10
+ Add `gem 'guardrail'` to your Gemfile.
13
11
 
14
12
  ## Usage
15
13
 
16
- There are two major use cases for guardrail. The first is for primary/replica(/deploy) environments.
17
- Using a replica is as simple as adding a replica block (underneath your main environment block) in
18
- database.yml, then wrapping stuff you want to query the replica in GuardRail.activate(:replica) blocks.
19
- You can extend this to a deploy environment so that migrations will run as a deploy user that
20
- permission to modify schema, while your normal app runs with lower privileges that cannot modify
21
- schema. This is defense-in-depth in practice, so that *if* you happen to have a SQL injection
22
- bug, it would be impossible to do something like dropping tables.
14
+ See https://guides.rubyonrails.org/active_record_multiple_databases.html. GuardRail simply adds
15
+ syntactic sugar to easily switch to different roles:
23
16
 
24
- The other major use case is more defense-in-depth. By carefully setting up your environment, you
25
- can default to script/console sessions for regular users to use their own database user, and the
26
- replica.
27
-
28
- Example database.yml file:
29
-
30
- ```yaml
31
- production:
32
- adapter: postgresql
33
- username: myapp
34
- database: myapp
35
- host: db-primary
36
- replica:
37
- host: db-replica
38
- deploy:
39
- username: deploy
40
- ```
41
-
42
- Using an initializer, you can achieve the default environment settings (in tandem with profile
43
- changes):
44
17
 
45
18
  ```ruby
46
- if ENV['RAILS_DATABASE_ENVIRONMENT']
47
- GuardRail.activate!(ENV['RAILS_DATABASE_ENVIRONMENT'].to_sym)
48
- end
49
- if ENV['RAILS_DATABASE_USER']
50
- GuardRail.apply_config!(:username => ENV['RAILS_DATABASE_USER'])
19
+ def some_method
20
+ GuardRails.activate(:secondary) do
21
+ MyModel.some_really_long_query
22
+ end
51
23
  end
52
24
  ```
53
25
 
54
- Additionally **in Ruby 2.0+** you can include GuardRail::HelperMethods and use several helpers
26
+ Additionally you can include GuardRail::HelperMethods and use several helpers
55
27
  to execute methods on specific environments:
56
28
 
57
29
  ```ruby
data/lib/guard_rail.rb CHANGED
@@ -1,114 +1,19 @@
1
- require 'set'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module GuardRail
4
4
  class << self
5
- attr_accessor :primary_environment_name
6
- GuardRail.primary_environment_name = :primary
7
-
8
5
  def environment
9
- Thread.current[:guard_rail_environment] ||= primary_environment_name
10
- end
11
-
12
- def global_config
13
- @global_config ||= {}
14
- end
15
-
16
- def activated_environments
17
- @activated_environments ||= Set.new()
18
- end
19
-
20
- # semi-private
21
- def initialize!
22
- require 'guard_rail/connection_handler'
23
- require 'guard_rail/connection_specification'
24
- require 'guard_rail/helper_methods'
25
-
26
- activated_environments << GuardRail.environment
27
-
28
- ActiveRecord::ConnectionAdapters::ConnectionHandler.prepend(ConnectionHandler)
29
- ActiveRecord::ConnectionAdapters::ConnectionSpecification.prepend(ConnectionSpecification)
30
- end
31
-
32
- def global_config_sequence
33
- @global_config_sequence ||= 1
34
- end
35
-
36
- def bump_sequence
37
- @global_config_sequence ||= 1
38
- @global_config_sequence += 1
39
- ActiveRecord::Base::connection_handler.clear_all_connections!
40
- end
41
-
42
- # for altering other pieces of config (i.e. username)
43
- # will force a disconnect
44
- def apply_config!(hash)
45
- global_config.merge!(hash)
46
- bump_sequence
47
- end
48
-
49
- def remove_config!(key)
50
- global_config.delete(key)
51
- bump_sequence
52
- end
53
-
54
- def connection_handlers
55
- save_handler
56
- @connection_handlers
57
- end
58
-
59
- # switch environment for the duration of the block
60
- # will keep the old connections around
61
- def activate(environment)
62
- environment ||= primary_environment_name
63
- return yield if environment == self.environment
64
- begin
65
- old_environment = activate!(environment)
66
- activated_environments << environment
67
- yield
68
- ensure
69
- Thread.current[:guard_rail_environment] = old_environment
70
- ActiveRecord::Base.connection_handler = ensure_handler unless test?
71
- end
6
+ ActiveRecord::Base.current_role
72
7
  end
73
8
 
74
- # for use from script/console ONLY
75
- def activate!(environment)
76
- environment ||= primary_environment_name
77
- save_handler
78
- old_environment = self.environment
79
- Thread.current[:guard_rail_environment] = environment
80
- ActiveRecord::Base.connection_handler = ensure_handler unless test?
81
- old_environment
9
+ def activate(role)
10
+ return yield if environment == role
11
+ ActiveRecord::Base.connected_to(role: role) { yield }
82
12
  end
83
13
 
84
- private
85
-
86
- def test?
87
- Rails.env.test?
88
- end
89
-
90
- def save_handler
91
- @connection_handlers ||= {}
92
- @connection_handlers[environment] ||= ActiveRecord::Base.connection_handler
93
- end
94
-
95
- def ensure_handler
96
- new_handler = @connection_handlers[environment]
97
- if !new_handler
98
- new_handler = @connection_handlers[environment] = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
99
- pools = ActiveRecord::Base.connection_handler.send(:owner_to_pool)
100
- pools.each_pair do |model, pool|
101
- new_handler.establish_connection(pool.spec.config)
102
- end
103
- end
104
- new_handler
14
+ def activate!(role)
15
+ return if environment == role
16
+ ActiveRecord::Base.connecting_to(role: role)
105
17
  end
106
18
  end
107
19
  end
108
-
109
- if defined?(Rails::Railtie)
110
- require "guard_rail/railtie"
111
- else
112
- # just load everything immediately for Rails 2
113
- GuardRail.initialize!
114
- end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GuardRail
4
- VERSION = "2.0.1"
4
+ VERSION = "3.0.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: guardrail
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-26 00:00:00.000000000 Z
11
+ date: 2021-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,40 +16,40 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '5.1'
19
+ version: '6.1'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '6.1'
22
+ version: '6.2'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '5.1'
29
+ version: '6.1'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '6.1'
32
+ version: '6.2'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: railties
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: '5.1'
39
+ version: '6.1'
40
40
  - - "<"
41
41
  - !ruby/object:Gem::Version
42
- version: '6.1'
42
+ version: '6.2'
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
46
46
  requirements:
47
47
  - - ">="
48
48
  - !ruby/object:Gem::Version
49
- version: '5.1'
49
+ version: '6.1'
50
50
  - - "<"
51
51
  - !ruby/object:Gem::Version
52
- version: '6.1'
52
+ version: '6.2'
53
53
  - !ruby/object:Gem::Dependency
54
54
  name: appraisal
55
55
  requirement: !ruby/object:Gem::Requirement
@@ -78,48 +78,6 @@ dependencies:
78
78
  - - ">="
79
79
  - !ruby/object:Gem::Version
80
80
  version: '0'
81
- - !ruby/object:Gem::Dependency
82
- name: mocha
83
- requirement: !ruby/object:Gem::Requirement
84
- requirements:
85
- - - ">="
86
- - !ruby/object:Gem::Version
87
- version: '0'
88
- type: :development
89
- prerelease: false
90
- version_requirements: !ruby/object:Gem::Requirement
91
- requirements:
92
- - - ">="
93
- - !ruby/object:Gem::Version
94
- version: '0'
95
- - !ruby/object:Gem::Dependency
96
- name: rspec
97
- requirement: !ruby/object:Gem::Requirement
98
- requirements:
99
- - - "~>"
100
- - !ruby/object:Gem::Version
101
- version: '3.0'
102
- type: :development
103
- prerelease: false
104
- version_requirements: !ruby/object:Gem::Requirement
105
- requirements:
106
- - - "~>"
107
- - !ruby/object:Gem::Version
108
- version: '3.0'
109
- - !ruby/object:Gem::Dependency
110
- name: sqlite3
111
- requirement: !ruby/object:Gem::Requirement
112
- requirements:
113
- - - ">="
114
- - !ruby/object:Gem::Version
115
- version: '0'
116
- type: :development
117
- prerelease: false
118
- version_requirements: !ruby/object:Gem::Requirement
119
- requirements:
120
- - - ">="
121
- - !ruby/object:Gem::Version
122
- version: '0'
123
81
  description: Allows multiple environments in database.yml, and dynamically switching
124
82
  them.
125
83
  email: cody@instructure.com
@@ -130,13 +88,9 @@ files:
130
88
  - LICENSE
131
89
  - README.md
132
90
  - lib/guard_rail.rb
133
- - lib/guard_rail/connection_handler.rb
134
- - lib/guard_rail/connection_specification.rb
135
91
  - lib/guard_rail/helper_methods.rb
136
- - lib/guard_rail/railtie.rb
137
92
  - lib/guard_rail/version.rb
138
93
  - lib/guardrail.rb
139
- - spec/guard_rail_spec.rb
140
94
  homepage: http://github.com/instructure/guardrail
141
95
  licenses:
142
96
  - MIT
@@ -160,5 +114,4 @@ rubygems_version: 3.1.4
160
114
  signing_key:
161
115
  specification_version: 4
162
116
  summary: ActiveRecord database environment switching for secondaries and least-privilege
163
- test_files:
164
- - spec/guard_rail_spec.rb
117
+ test_files: []
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GuardRail
4
- module ConnectionHandler
5
- %w{clear_active_connections clear_reloadable_connections
6
- clear_all_connections verify_active_connections }.each do |method|
7
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
8
- def #{method}!(super_method: false)
9
- return super() if super_method
10
- ::GuardRail.connection_handlers.values.each { |handler| handler.#{method}!(super_method: true) }
11
- end
12
- RUBY
13
- end
14
- end
15
- end
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'i18n/core_ext/hash' unless Hash.method_defined?(:deep_symbolize_keys)
4
-
5
- module GuardRail
6
- module ConnectionSpecification
7
- class CacheCoherentHash < Hash
8
- def initialize(spec)
9
- @spec = spec
10
- super
11
- end
12
-
13
- def []=(key, value)
14
- super
15
- @spec.instance_variable_set(:@current_config, nil)
16
- @spec.instance_variable_get(:@config)[key] = value
17
- end
18
-
19
- def delete(key)
20
- super
21
- @spec.instance_variable_set(:@current_config, nil)
22
- @spec.instance_variable_get(:@config).delete(key)
23
- end
24
-
25
- def dup
26
- Hash[self]
27
- end
28
-
29
- # in rails 4.2, active support tries to create a copy of the original object's class
30
- # instead of making a new Hash object, so it fails since initialize expects an argument
31
- def transform_keys(&block)
32
- dup.transform_keys(&block)
33
- end
34
- end
35
-
36
- def initialize(name, config, adapter_method)
37
- super(name, config.deep_symbolize_keys, adapter_method)
38
- end
39
-
40
- def initialize_dup(original)
41
- @current_config = nil
42
- super
43
- end
44
-
45
- def config
46
- @current_config = nil if GuardRail.environment != @current_config_environment || GuardRail.global_config_sequence != @current_config_sequence
47
- return @current_config if @current_config
48
-
49
- @current_config_environment = GuardRail.environment
50
- @current_config_sequence = GuardRail.global_config_sequence
51
- config = @config.dup
52
- if @config.has_key?(GuardRail.environment)
53
- env_config = @config[GuardRail.environment]
54
- # an array of databases for this environment; for now, just choose the first non-nil element
55
- if env_config.is_a?(Array)
56
- env_config = env_config.detect { |individual_config| !individual_config.nil? }
57
- end
58
- config.merge!(env_config.symbolize_keys)
59
- end
60
-
61
- config.keys.each do |key|
62
- next unless config[key].is_a?(String)
63
- config[key] = config[key] % config
64
- end
65
-
66
- config.merge!(GuardRail.global_config)
67
-
68
- @current_config = CacheCoherentHash.new(self)
69
- @current_config.replace(config)
70
-
71
- @current_config
72
- end
73
-
74
- def config=(value)
75
- @config = value
76
- @current_config = nil
77
- end
78
- end
79
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GuardRail
4
- class Railtie < Rails::Railtie
5
- initializer "guard_rail.extend_ar", :before => "active_record.initialize_database" do
6
- ActiveSupport.on_load(:active_record) do
7
- GuardRail.initialize!
8
- end
9
- end
10
- end
11
- end
@@ -1,169 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_record'
4
- require 'byebug'
5
- require 'rails'
6
- require 'guard_rail'
7
-
8
- # we're not actually bringing up ActiveRecord, so we need to initialize our stuff
9
- GuardRail.initialize!
10
-
11
- RSpec.configure do |config|
12
- config.mock_framework = :mocha
13
- end
14
-
15
- describe GuardRail do
16
- ConnectionSpecification = ActiveRecord::ConnectionAdapters::ConnectionSpecification
17
-
18
- def spec_args(conf, adapter)
19
- ['dummy', conf, adapter]
20
- end
21
-
22
- it "should allow changing environments" do
23
- conf = {
24
- :adapter => 'postgresql',
25
- :database => 'primary',
26
- :username => 'canvas',
27
- :deploy => {
28
- :username => 'deploy'
29
- },
30
- :replica => {
31
- :database => 'replica'
32
- }
33
- }
34
- spec = ConnectionSpecification.new(*spec_args(conf, 'adapter'))
35
- expect(spec.config[:username]).to eq('canvas')
36
- expect(spec.config[:database]).to eq('primary')
37
- GuardRail.activate(:deploy) do
38
- expect(spec.config[:username]).to eq('deploy')
39
- expect(spec.config[:database]).to eq('primary')
40
- end
41
- expect(spec.config[:username]).to eq('canvas')
42
- expect(spec.config[:database]).to eq('primary')
43
- GuardRail.activate(:replica) do
44
- expect(spec.config[:username]).to eq('canvas')
45
- expect(spec.config[:database]).to eq('replica')
46
- end
47
- expect(spec.config[:username]).to eq('canvas')
48
- expect(spec.config[:database]).to eq('primary')
49
- end
50
-
51
- it "should allow using hash insertions" do
52
- conf = {
53
- :adapter => 'postgresql',
54
- :database => 'primary',
55
- :username => '%{schema_search_path}',
56
- :schema_search_path => 'canvas',
57
- :deploy => {
58
- :username => 'deploy'
59
- }
60
- }
61
- spec = ConnectionSpecification.new(*spec_args(conf, 'adapter'))
62
- expect(spec.config[:username]).to eq('canvas')
63
- GuardRail.activate(:deploy) do
64
- expect(spec.config[:username]).to eq('deploy')
65
- end
66
- expect(spec.config[:username]).to eq('canvas')
67
- end
68
-
69
- it "should be cache coherent with modifying the config" do
70
- conf = {
71
- :adapter => 'postgresql',
72
- :database => 'primary',
73
- :username => '%{schema_search_path}',
74
- :schema_search_path => 'canvas',
75
- :deploy => {
76
- :username => 'deploy'
77
- }
78
- }
79
- spec = ConnectionSpecification.new(*spec_args(conf.dup, 'adapter'))
80
- expect(spec.config[:username]).to eq('canvas')
81
- spec.config[:schema_search_path] = 'bob'
82
- expect(spec.config[:schema_search_path]).to eq('bob')
83
- expect(spec.config[:username]).to eq('bob')
84
- GuardRail.activate(:deploy) do
85
- expect(spec.config[:schema_search_path]).to eq('bob')
86
- expect(spec.config[:username]).to eq('deploy')
87
- end
88
- external_config = spec.config.dup
89
- expect(external_config.class).to eq(Hash)
90
- expect(external_config).to eq(spec.config)
91
-
92
- spec.config = conf.dup
93
- expect(spec.config[:username]).to eq('canvas')
94
- end
95
-
96
- it "does not share config objects when dup'ing specs" do
97
- conf = {
98
- :adapter => 'postgresql',
99
- :database => 'primary',
100
- :username => '%{schema_search_path}',
101
- :schema_search_path => 'canvas',
102
- :deploy => {
103
- :username => 'deploy'
104
- }
105
- }
106
- spec = ConnectionSpecification.new(*spec_args(conf.dup, 'adapter'))
107
- expect(spec.config.object_id).not_to eq spec.dup.config.object_id
108
- end
109
-
110
- describe "activate" do
111
- before do
112
- #!!! trick it in to actually switching envs
113
- Rails.env.stubs(:test?).returns(false)
114
-
115
- # be sure to test bugs where the current env isn't yet included in this hash
116
- GuardRail.connection_handlers.clear
117
-
118
- ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
119
- end
120
-
121
- it "should call ensure_handler when switching envs" do
122
- old_handler = ActiveRecord::Base.connection_handler
123
- GuardRail.expects(:ensure_handler).returns(old_handler).twice
124
- GuardRail.activate(:replica) {}
125
- end
126
-
127
- it "should not close connections when switching envs" do
128
- conn = ActiveRecord::Base.connection
129
- replica_conn = GuardRail.activate(:replica) { ActiveRecord::Base.connection }
130
- expect(conn).not_to eq(replica_conn)
131
- expect(ActiveRecord::Base.connection).to eq(conn)
132
- end
133
-
134
- it "should track all activated environments" do
135
- GuardRail.activate(:replica) {}
136
- GuardRail.activate(:custom) {}
137
- expected = Set.new([:primary, :replica, :custom])
138
- expect(GuardRail.activated_environments & expected).to eq(expected)
139
- end
140
-
141
- context "non-transactional" do
142
- it "should really disconnect all envs" do
143
- ActiveRecord::Base.connection
144
- expect(ActiveRecord::Base.connection_pool).to be_connected
145
-
146
- GuardRail.activate(:replica) do
147
- ActiveRecord::Base.connection
148
- expect(ActiveRecord::Base.connection_pool).to be_connected
149
- end
150
-
151
- ActiveRecord::Base.clear_all_connections!
152
- expect(ActiveRecord::Base.connection_pool).not_to be_connected
153
- GuardRail.activate(:replica) do
154
- expect(ActiveRecord::Base.connection_pool).not_to be_connected
155
- end
156
- end
157
- end
158
-
159
- it 'is thread safe' do
160
- GuardRail.activate(:replica) do
161
- Thread.new do
162
- GuardRail.activate!(:deploy)
163
- expect(GuardRail.environment).to eq :deploy
164
- end.join
165
- expect(GuardRail.environment).to eq :replica
166
- end
167
- end
168
- end
169
- end