guardrail 2.0.0 → 3.0.1

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: 737326a16630468262a493961348ecc21dd8b4f8d82c6526bfafe1c0b66c294c
4
- data.tar.gz: c90574a6e50c6556e678b08acc766ef74c36be693e6d012989bb6fcb71e93c77
3
+ metadata.gz: 356cff039858ba07cbc234ab54cf80c96c8211a2dea702c020a48fd108dc87ae
4
+ data.tar.gz: 768e3d4069f07db8e7c0d67eb1c858a826977c0e8261604217fbd87580afb33b
5
5
  SHA512:
6
- metadata.gz: d9761c6d3817b19a980827c71fceca3051cc8cbaf2b86bd959bf110f954a18c271e433bd433d92352d455e6af38234a1945369bbac118b142e1bcdfe45bbd78d
7
- data.tar.gz: 20aab4abcd5ea1743df249d58d7772ac186277d6e8eb25e9772dd7aed46e41fabc1ac1870a4e802c048428f27f2bb7eb38bbbeffc1c951dd22e177c5f92d54ab
6
+ metadata.gz: 9aab9b92d9de1ecd7867085b2938686e02e4ef3caa37a04b9e2d8ddb0ebcb3395cfca0e95e14559a5694ef3e0b5de83d61478eaccac1c9169240e0c521fb72c8
7
+ data.tar.gz: '02464494bbb4c25425154b4c9bb1958938becb94da6678bfe6af2baf42b557cb4be62b75bf79aae280eee685629c8fb5799298f3681aeceb72a9b321aac03de7'
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GuardRail
2
4
  module HelperMethods
3
5
  def self.included(base)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GuardRail
2
- VERSION = "2.0.0"
4
+ VERSION = "3.0.1"
3
5
  end
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
data/lib/guardrail.rb CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'guard_rail'
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.0
4
+ version: 3.0.1
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-05 00:00:00.000000000 Z
11
+ date: 2022-03-17 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: '7.1'
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: '7.1'
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: '7.1'
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: '7.1'
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
@@ -156,9 +110,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
110
  - !ruby/object:Gem::Version
157
111
  version: '0'
158
112
  requirements: []
159
- rubygems_version: 3.1.2
113
+ 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,13 +0,0 @@
1
- module GuardRail
2
- module ConnectionHandler
3
- %w{clear_active_connections clear_reloadable_connections
4
- clear_all_connections verify_active_connections }.each do |method|
5
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
6
- def #{method}!(super_method: false)
7
- return super() if super_method
8
- ::GuardRail.connection_handlers.values.each { |handler| handler.#{method}!(super_method: true) }
9
- end
10
- RUBY
11
- end
12
- end
13
- end
@@ -1,77 +0,0 @@
1
- require 'i18n/core_ext/hash' unless Hash.method_defined?(:deep_symbolize_keys)
2
-
3
- module GuardRail
4
- module ConnectionSpecification
5
- class CacheCoherentHash < Hash
6
- def initialize(spec)
7
- @spec = spec
8
- super
9
- end
10
-
11
- def []=(key, value)
12
- super
13
- @spec.instance_variable_set(:@current_config, nil)
14
- @spec.instance_variable_get(:@config)[key] = value
15
- end
16
-
17
- def delete(key)
18
- super
19
- @spec.instance_variable_set(:@current_config, nil)
20
- @spec.instance_variable_get(:@config).delete(key)
21
- end
22
-
23
- def dup
24
- Hash[self]
25
- end
26
-
27
- # in rails 4.2, active support tries to create a copy of the original object's class
28
- # instead of making a new Hash object, so it fails since initialize expects an argument
29
- def transform_keys(&block)
30
- dup.transform_keys(&block)
31
- end
32
- end
33
-
34
- def initialize(name, config, adapter_method)
35
- super(name, config.deep_symbolize_keys, adapter_method)
36
- end
37
-
38
- def initialize_dup(original)
39
- @current_config = nil
40
- super
41
- end
42
-
43
- def config
44
- @current_config = nil if GuardRail.environment != @current_config_environment || GuardRail.global_config_sequence != @current_config_sequence
45
- return @current_config if @current_config
46
-
47
- @current_config_environment = GuardRail.environment
48
- @current_config_sequence = GuardRail.global_config_sequence
49
- config = @config.dup
50
- if @config.has_key?(GuardRail.environment)
51
- env_config = @config[GuardRail.environment]
52
- # an array of databases for this environment; for now, just choose the first non-nil element
53
- if env_config.is_a?(Array)
54
- env_config = env_config.detect { |individual_config| !individual_config.nil? }
55
- end
56
- config.merge!(env_config.symbolize_keys)
57
- end
58
-
59
- config.keys.each do |key|
60
- next unless config[key].is_a?(String)
61
- config[key] = config[key] % config
62
- end
63
-
64
- config.merge!(GuardRail.global_config)
65
-
66
- @current_config = CacheCoherentHash.new(self)
67
- @current_config.replace(config)
68
-
69
- @current_config
70
- end
71
-
72
- def config=(value)
73
- @config = value
74
- @current_config = nil
75
- end
76
- end
77
- end
@@ -1,9 +0,0 @@
1
- module GuardRail
2
- class Railtie < Rails::Railtie
3
- initializer "guard_rail.extend_ar", :before => "active_record.initialize_database" do
4
- ActiveSupport.on_load(:active_record) do
5
- GuardRail.initialize!
6
- end
7
- end
8
- end
9
- end
@@ -1,167 +0,0 @@
1
- require 'active_record'
2
- require 'byebug'
3
- require 'rails'
4
- require 'guard_rail'
5
-
6
- # we're not actually bringing up ActiveRecord, so we need to initialize our stuff
7
- GuardRail.initialize!
8
-
9
- RSpec.configure do |config|
10
- config.mock_framework = :mocha
11
- end
12
-
13
- describe GuardRail do
14
- ConnectionSpecification = ActiveRecord::ConnectionAdapters::ConnectionSpecification
15
-
16
- def spec_args(conf, adapter)
17
- ['dummy', conf, adapter]
18
- end
19
-
20
- it "should allow changing environments" do
21
- conf = {
22
- :adapter => 'postgresql',
23
- :database => 'primary',
24
- :username => 'canvas',
25
- :deploy => {
26
- :username => 'deploy'
27
- },
28
- :replica => {
29
- :database => 'replica'
30
- }
31
- }
32
- spec = ConnectionSpecification.new(*spec_args(conf, 'adapter'))
33
- expect(spec.config[:username]).to eq('canvas')
34
- expect(spec.config[:database]).to eq('primary')
35
- GuardRail.activate(:deploy) do
36
- expect(spec.config[:username]).to eq('deploy')
37
- expect(spec.config[:database]).to eq('primary')
38
- end
39
- expect(spec.config[:username]).to eq('canvas')
40
- expect(spec.config[:database]).to eq('primary')
41
- GuardRail.activate(:replica) do
42
- expect(spec.config[:username]).to eq('canvas')
43
- expect(spec.config[:database]).to eq('replica')
44
- end
45
- expect(spec.config[:username]).to eq('canvas')
46
- expect(spec.config[:database]).to eq('primary')
47
- end
48
-
49
- it "should allow using hash insertions" do
50
- conf = {
51
- :adapter => 'postgresql',
52
- :database => 'primary',
53
- :username => '%{schema_search_path}',
54
- :schema_search_path => 'canvas',
55
- :deploy => {
56
- :username => 'deploy'
57
- }
58
- }
59
- spec = ConnectionSpecification.new(*spec_args(conf, 'adapter'))
60
- expect(spec.config[:username]).to eq('canvas')
61
- GuardRail.activate(:deploy) do
62
- expect(spec.config[:username]).to eq('deploy')
63
- end
64
- expect(spec.config[:username]).to eq('canvas')
65
- end
66
-
67
- it "should be cache coherent with modifying the config" do
68
- conf = {
69
- :adapter => 'postgresql',
70
- :database => 'primary',
71
- :username => '%{schema_search_path}',
72
- :schema_search_path => 'canvas',
73
- :deploy => {
74
- :username => 'deploy'
75
- }
76
- }
77
- spec = ConnectionSpecification.new(*spec_args(conf.dup, 'adapter'))
78
- expect(spec.config[:username]).to eq('canvas')
79
- spec.config[:schema_search_path] = 'bob'
80
- expect(spec.config[:schema_search_path]).to eq('bob')
81
- expect(spec.config[:username]).to eq('bob')
82
- GuardRail.activate(:deploy) do
83
- expect(spec.config[:schema_search_path]).to eq('bob')
84
- expect(spec.config[:username]).to eq('deploy')
85
- end
86
- external_config = spec.config.dup
87
- expect(external_config.class).to eq(Hash)
88
- expect(external_config).to eq(spec.config)
89
-
90
- spec.config = conf.dup
91
- expect(spec.config[:username]).to eq('canvas')
92
- end
93
-
94
- it "does not share config objects when dup'ing specs" do
95
- conf = {
96
- :adapter => 'postgresql',
97
- :database => 'primary',
98
- :username => '%{schema_search_path}',
99
- :schema_search_path => 'canvas',
100
- :deploy => {
101
- :username => 'deploy'
102
- }
103
- }
104
- spec = ConnectionSpecification.new(*spec_args(conf.dup, 'adapter'))
105
- expect(spec.config.object_id).not_to eq spec.dup.config.object_id
106
- end
107
-
108
- describe "activate" do
109
- before do
110
- #!!! trick it in to actually switching envs
111
- Rails.env.stubs(:test?).returns(false)
112
-
113
- # be sure to test bugs where the current env isn't yet included in this hash
114
- GuardRail.connection_handlers.clear
115
-
116
- ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
117
- end
118
-
119
- it "should call ensure_handler when switching envs" do
120
- old_handler = ActiveRecord::Base.connection_handler
121
- GuardRail.expects(:ensure_handler).returns(old_handler).twice
122
- GuardRail.activate(:replica) {}
123
- end
124
-
125
- it "should not close connections when switching envs" do
126
- conn = ActiveRecord::Base.connection
127
- replica_conn = GuardRail.activate(:replica) { ActiveRecord::Base.connection }
128
- expect(conn).not_to eq(replica_conn)
129
- expect(ActiveRecord::Base.connection).to eq(conn)
130
- end
131
-
132
- it "should track all activated environments" do
133
- GuardRail.activate(:replica) {}
134
- GuardRail.activate(:custom) {}
135
- expected = Set.new([:primary, :replica, :custom])
136
- expect(GuardRail.activated_environments & expected).to eq(expected)
137
- end
138
-
139
- context "non-transactional" do
140
- it "should really disconnect all envs" do
141
- ActiveRecord::Base.connection
142
- expect(ActiveRecord::Base.connection_pool).to be_connected
143
-
144
- GuardRail.activate(:replica) do
145
- ActiveRecord::Base.connection
146
- expect(ActiveRecord::Base.connection_pool).to be_connected
147
- end
148
-
149
- ActiveRecord::Base.clear_all_connections!
150
- expect(ActiveRecord::Base.connection_pool).not_to be_connected
151
- GuardRail.activate(:replica) do
152
- expect(ActiveRecord::Base.connection_pool).not_to be_connected
153
- end
154
- end
155
- end
156
-
157
- it 'is thread safe' do
158
- GuardRail.activate(:replica) do
159
- Thread.new do
160
- GuardRail.activate!(:deploy)
161
- expect(GuardRail.environment).to eq :deploy
162
- end.join
163
- expect(GuardRail.environment).to eq :replica
164
- end
165
- end
166
- end
167
- end