guardrail 2.0.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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