roo_on_rails 1.7.0 → 1.8.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
  SHA1:
3
- metadata.gz: 8adca9db27e5e65ab629420b4d02f4900a9c9629
4
- data.tar.gz: e50c1756717e4f484684ae31de1653527ee1b081
3
+ metadata.gz: 3c14df25a7b6424101a9e98b40db865b5481b507
4
+ data.tar.gz: dfff2f9bea33530d06533183c2d31be578d0aa96
5
5
  SHA512:
6
- metadata.gz: fc9a1b2dfc35fff8b74f8a749e6f2858ac8c41d7a8d20b012d2d75d107581bc2643ec44ab2c01ec125f1419c92af06cbad55dca42b9eaa08f8487e554c0d62d9
7
- data.tar.gz: 3d8545de35da3827a95ba6ad2d74367d662e4d09e5f0b900aaa567cf5f200b61ea0a7ed6f5110e64ccd0e81c82dbc59160ce877010d55a03a38d0540909093b7
6
+ metadata.gz: 8c8811e21f02b5eaf1ce83a6653e5e0329c8c0a80f9ef9cf80e407576409c57818ed90662d4ced4b1f44bc6ac51a46dff57376af82a6d8f905d66069acfa6205
7
+ data.tar.gz: 5e5b53d7ca2df0137cc3326c13231242f30d2ae9743024e34131ad6b9ad167aa429f746d205484e9995b472a2317e235ea528d47de73af14864ca7989dda72ae
data/.rubocop.yml CHANGED
@@ -28,7 +28,7 @@ Metrics/LineLength:
28
28
  Max: 100
29
29
  Exclude:
30
30
  - Gemfile
31
-
31
+
32
32
  Style/Documentation:
33
33
  Enabled: false
34
34
 
@@ -39,3 +39,8 @@ Style/DotPosition:
39
39
  Enabled: false
40
40
  Metrics/AbcSize:
41
41
  Max: 30
42
+
43
+ Rubocop/Metrics/MethodLength:
44
+ Max: 20
45
+ Rubocop/Style/TrailingCommaInLiteral:
46
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ # v1.8.0 (2017-07-26)
2
+
3
+ Bug fixes:
4
+
5
+ - Do not consider 'quiet' workers in the SLA sidekiq metric (#51)
6
+
7
+ Features:
8
+
9
+ - Provides a `PLAYBOOK.md` template when detected missing (#42)
10
+ - Adds pre-baked Google OAuth support (#44, #49)
11
+ - Reports Sidekiq metrics only for queues defined in process (#50)
12
+ - Finer-grained Sidekiq configuration (#46)
13
+ - adds 'default' to list of default Sidekiq queues
14
+ - accepts custom Sidekiq queue names and permitted latency values
15
+ - allows environment-specific application checks
16
+
17
+ Other:
18
+
19
+ - Fixes the test harness (#48)
20
+
1
21
  # v1.7.0 (2017-07-18)
2
22
 
3
23
  Features:
@@ -0,0 +1,84 @@
1
+ ## Using the Google OAuth feature
2
+
3
+ This feature is only supported when using Rails 5+.
4
+
5
+ `roo_on_rails` provides a pre-baked Omniauth setup to help protect your app with
6
+ Google authentication. Read the main `README` first to set things up; you'll
7
+ need at least `GOOGLE_AUTH_ENABLED=YES`, and `GOOGLE_AUTH_CLIENT_ID` and
8
+ `GOOGLE_AUTH_CLIENT_SECRET` configured.
9
+
10
+ Let's build a tiny app that has just a homepage, prompts you to sign in, and
11
+ show your email once you have.
12
+
13
+ We add the landing page route:
14
+
15
+ ```ruby
16
+ # config/routes.rb
17
+ Rails.application.routes.draw do
18
+ root to: 'landing#index'
19
+ end
20
+ ```
21
+
22
+ And a controller/view:
23
+
24
+ ```ruby
25
+ # app/controllers/landing_controller.rb
26
+ class LandingController < ApplicationController
27
+ def index
28
+ if session[:email]
29
+ render inline: %{
30
+ You are logged in as <%= session[:email] %>! <br/>
31
+ <%= link_to 'Logout', auth_logout_path %>
32
+ }
33
+ else
34
+ render inline: %{
35
+ You are not logged in <br/>
36
+ <%= link_to 'Login', auth_google_oauth2_path %>
37
+ }
38
+ end
39
+ end
40
+ end
41
+ ```
42
+
43
+ The authentication routes get added by `roo_on_rails`; we need to implement at
44
+ least session creation, destruction, and handling of failure:
45
+
46
+ ```ruby
47
+ # app/controllers/sessions_controller.rb
48
+
49
+ class SessionsController < ApplicationController
50
+ def create
51
+ auth_data = request.env['omniauth.auth']
52
+ session[:email] = auth_data.info.email.downcase
53
+ redirect_to root_path
54
+ end
55
+
56
+ def destroy
57
+ session.clear
58
+ redirect_to root_path
59
+ end
60
+
61
+ def failure
62
+ @error = env['omniauth.error']
63
+ render inline: %{
64
+ Authentication failed: <br/>
65
+ <%= @error.class.name %> <br/>
66
+ <%= @error.message %>
67
+ }
68
+ end
69
+ end
70
+ ```
71
+
72
+ And that's it. If you want to blanket-protect a controller, an idiomatic way
73
+ would be to:
74
+
75
+ ```ruby
76
+ before_filter { redirect_to auth_google_oauth2_path unless session[:email] }
77
+ ```
78
+
79
+ If you dislike the name `SessionsController`, you can update
80
+ `GOOGLE_AUTH_CONTROLLER` to point to a different controller.
81
+
82
+ You can also change the `/auth` path prefix used by this feature; in this case
83
+ you'll want to update the example above. For instance, if you change `/auth` to
84
+ `/prefix`, `auth_google_oauth2_path` becomes `prefix_google_oauth2_path`.
data/README.md CHANGED
@@ -103,6 +103,11 @@ statement timeouts directly in the database._
103
103
 
104
104
  ### Sidekiq
105
105
 
106
+ Deliveroo services implement Sidekiq with an _urgency_ pattern. By only having
107
+ time-based [SLA](https://en.wikipedia.org/wiki/Service-level_agreement) queue
108
+ names (eg. `within5minutes`) we can automatically create incident alerting for
109
+ queues which take longer than the time the application needs them to be processed.
110
+
106
111
  When `SIDEKIQ_ENABLED` is set we'll:
107
112
 
108
113
  - check for the existence of a worker line in your Procfile;
@@ -112,11 +117,16 @@ When `SIDEKIQ_ENABLED` is set we'll:
112
117
  The following ENV are available:
113
118
 
114
119
  - `SIDEKIQ_ENABLED`
120
+ - `SIDEKIQ_QUEUES` - comma-separated custom queue names; if not specified, default queues are processed which are defined [here](./lib/roo_on_rails/sidekiq/settings.rb). For accurate health reporting and scaling for your custom queue names, you can specify their permitted latency within this variable e.g. `within5seconds,queue-one:10seconds,queue-two:20minutes,queue-three:3hours,queue-four:1day,default`. For non-default queue names that don't follow the `withinXunit` pattern, you will **need** to specify the permitted latency otherwise the initialization will error.
115
121
  - `SIDEKIQ_THREADS` (default: 25) - Sets sidekiq concurrency value
116
122
  - `SIDEKIQ_DATABASE_REAPING_FREQUENCY` (default: 10) - For sidekiq processes the
117
123
  amount of time in seconds rails will wait before attempting to find and
118
124
  recover connections from dead threads
119
125
 
126
+ NB. If you are migrating to SLA-based queue names, do not set `SIDEKIQ_ENABLED`
127
+ to `true` before your old queues have finished processing (this will prevent
128
+ Sidekiq from seeing the old queues at all).
129
+
120
130
  ### HireFire (for Sidekiq workers)
121
131
 
122
132
  When `HIREFIRE_TOKEN` is set an endpoint will be mounted at `/hirefire` that
@@ -173,25 +183,21 @@ logger.with(a: 1, b: 2).info('Stuff')
173
183
  See the [class documentation](lib/roo_on_rails/context_logging.rb) for further
174
184
  details.
175
185
 
176
- ### Google Oauth
186
+ ### Google OAuth authentication
187
+
188
+ When `GOOGLE_AUTH_ENABLED` is set to true we inject a `Omniauth` Rack middleware
189
+ with a pre-configured strategy for Google Oauth2.
177
190
 
178
- When `GOOGLE_AUTH_ENABLED` is set to true we'll:
191
+ Parameters:
179
192
 
180
- * Inject a `Omniauth` Rack middleware with a pre-configured strategy for Google
181
- Oauth2.
182
- * Onject custom Rack middleare to handle Oauth callback requests.
183
- * Generate the `config/initializers/google_oauth.rb` file that contains some
184
- examples of how to wire in your authentication logic.
193
+ - `GOOGLE_AUTH_CLIENT_ID` and `GOOGLE_AUTH_CLIENT_SECRET` (mandatory)
194
+ - `GOOGLE_AUTH_PATH_PREFIX` (optional, defaults to `/auth`)
195
+ - `GOOGLE_AUTH_CONTROLLER` (optional, defaults to `sessions`)
185
196
 
186
- To use this functionality, you must:
197
+ This feature is bring-your-own-controller it won't magically protect your
198
+ application.
187
199
 
188
- * Obtain the Oauth2 credentials from Google and configure them in
189
- `GOOGLE_AUTH_CLIENT_ID` and `GOOGLE_AUTH_CLIENT_SECRET`.
190
- * Provide in `GOOGLE_AUTH_ALLOWED_DOMAINS` a comma-separated list of domains, to
191
- whitelist the allowed email addresses.
192
- * Customize the code in the generated Rails initializer to hook into your
193
- application's authentication logic.
194
- * Update your Rails controllers to require authentication, when necessary.
200
+ A simple but secure example is detailed in `README.google_oauth2.md`.
195
201
 
196
202
 
197
203
  ## Command features
@@ -204,9 +210,23 @@ Run the following from your app's top-level directory:
204
210
  bundle exec roo_on_rails
205
211
  ```
206
212
 
207
- You will (currently) need to have admin privileges on the
208
- `roo-dd-bridge-production` application for this to work. This will be addressed
209
- eventually.
213
+ That command will sequentially run a number of checks. For it to run successfully, you will need:
214
+
215
+ - a GitHub API token that can read your GitHub repository's settings placed in `~/.roo_on_rails/github-token`
216
+ - the Heroku toolbelt installed and logged in
217
+ - admin privileges on the `roo-dd-bridge-production` (this will be addressed eventually)
218
+ - checks are run sequentially for staging and then for production. The process halts at any non-fixable failing check. To process only specific environments, you can set a config variable while running the command, like so:
219
+
220
+ ```
221
+ # the default behaviour:
222
+ ROO_ON_RAILS_ENVIRONMENTS=staging,production bundle exec roo_on_rails
223
+
224
+ # run checks only on staging:
225
+ ROO_ON_RAILS_ENVIRONMENTS=staging bundle exec roo_on_rails
226
+
227
+ # run checks only on production:
228
+ ROO_ON_RAILS_ENVIRONMENTS=production bundle exec roo_on_rails
229
+ ```
210
230
 
211
231
 
212
232
  ### Description
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- roo_on_rails (1.7.0)
4
+ roo_on_rails (1.8.0)
5
5
  dogstatsd-ruby
6
6
  dotenv-rails (~> 2.1)
7
7
  faraday
@@ -74,7 +74,7 @@ GEM
74
74
  railties (>= 3.2, < 5.2)
75
75
  erubis (2.7.0)
76
76
  excon (0.57.1)
77
- faraday (0.12.1)
77
+ faraday (0.12.2)
78
78
  multipart-post (>= 1.2, < 3)
79
79
  faraday_middleware (0.11.0.1)
80
80
  faraday (>= 0.7.4, < 1.0)
@@ -137,7 +137,7 @@ GEM
137
137
  omniauth (1.4.2)
138
138
  hashie (>= 1.2, < 4)
139
139
  rack (>= 1.0, < 3)
140
- omniauth-google-oauth2 (0.5.0)
140
+ omniauth-google-oauth2 (0.5.1)
141
141
  jwt (~> 1.5)
142
142
  multi_json (~> 1.3)
143
143
  omniauth (>= 1.1.1)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- roo_on_rails (1.7.0)
4
+ roo_on_rails (1.8.0)
5
5
  dogstatsd-ruby
6
6
  dotenv-rails (~> 2.1)
7
7
  faraday
@@ -81,7 +81,7 @@ GEM
81
81
  railties (>= 3.2, < 5.2)
82
82
  erubis (2.7.0)
83
83
  excon (0.57.1)
84
- faraday (0.12.1)
84
+ faraday (0.12.2)
85
85
  multipart-post (>= 1.2, < 3)
86
86
  faraday_middleware (0.11.0.1)
87
87
  faraday (>= 0.7.4, < 1.0)
@@ -151,7 +151,7 @@ GEM
151
151
  omniauth (1.6.1)
152
152
  hashie (>= 3.4.6, < 3.6.0)
153
153
  rack (>= 1.6.2, < 3)
154
- omniauth-google-oauth2 (0.5.0)
154
+ omniauth-google-oauth2 (0.5.1)
155
155
  jwt (~> 1.5)
156
156
  multi_json (~> 1.3)
157
157
  omniauth (>= 1.1.1)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- roo_on_rails (1.7.0)
4
+ roo_on_rails (1.8.0)
5
5
  dogstatsd-ruby
6
6
  dotenv-rails (~> 2.1)
7
7
  faraday
@@ -84,7 +84,7 @@ GEM
84
84
  railties (>= 3.2, < 5.2)
85
85
  erubis (2.7.0)
86
86
  excon (0.57.1)
87
- faraday (0.12.1)
87
+ faraday (0.12.2)
88
88
  multipart-post (>= 1.2, < 3)
89
89
  faraday_middleware (0.11.0.1)
90
90
  faraday (>= 0.7.4, < 1.0)
@@ -155,7 +155,7 @@ GEM
155
155
  omniauth (1.6.1)
156
156
  hashie (>= 3.4.6, < 3.6.0)
157
157
  rack (>= 1.6.2, < 3)
158
- omniauth-google-oauth2 (0.5.0)
158
+ omniauth-google-oauth2 (0.5.1)
159
159
  jwt (~> 1.5)
160
160
  multi_json (~> 1.3)
161
161
  omniauth (>= 1.1.1)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- roo_on_rails (1.7.0)
4
+ roo_on_rails (1.8.0)
5
5
  dogstatsd-ruby
6
6
  dotenv-rails (~> 2.1)
7
7
  faraday
@@ -85,7 +85,7 @@ GEM
85
85
  erubi (1.6.0)
86
86
  erubis (2.7.0)
87
87
  excon (0.57.1)
88
- faraday (0.12.1)
88
+ faraday (0.12.2)
89
89
  multipart-post (>= 1.2, < 3)
90
90
  faraday_middleware (0.11.0.1)
91
91
  faraday (>= 0.7.4, < 1.0)
@@ -156,7 +156,7 @@ GEM
156
156
  omniauth (1.6.1)
157
157
  hashie (>= 3.4.6, < 3.6.0)
158
158
  rack (>= 1.6.2, < 3)
159
- omniauth-google-oauth2 (0.5.0)
159
+ omniauth-google-oauth2 (0.5.1)
160
160
  jwt (~> 1.5)
161
161
  multi_json (~> 1.3)
162
162
  omniauth (>= 1.1.1)
@@ -20,10 +20,12 @@ module RooOnRails
20
20
  @fix = @options.delete(:fix) { false }
21
21
  @context = @options.delete(:context) { Hashie::Mash.new }
22
22
  @shell = @options.delete(:shell) { Shell.new }
23
+ @dry_run = options.fetch(:dry_run, false)
23
24
  end
24
25
 
25
26
  def run
26
27
  resolve dependencies
28
+ return true if @dry_run
27
29
  say intro
28
30
  call
29
31
  true
@@ -1,4 +1,5 @@
1
1
  require 'roo_on_rails/checks/base'
2
+ require 'fileutils'
2
3
 
3
4
  module RooOnRails
4
5
  module Checks
@@ -11,11 +12,32 @@ module RooOnRails
11
12
  end
12
13
 
13
14
  def call
14
- if File.exist?(LOCATION)
15
- pass 'playbook found, legion on-call engineers thank you.'
16
- else
17
- fail! "please create a playbook at #{LOCATION}."
15
+ fail! "no playbook at #{LOCATION}." if playbook_missing?
16
+ fail! 'playbook still contains FIXME template sections' if playbook_unfinished?
17
+ pass 'playbook found, legion on-call engineers thank you.'
18
+ end
19
+
20
+ def fix
21
+ if !playbook_missing? && playbook_unfinished?
22
+ fail! 'please add detail to your playbook, removing FIXME sections'
18
23
  end
24
+
25
+ FileUtils.cp(
26
+ File.join(__dir__, 'playbook_template.md'),
27
+ LOCATION
28
+ )
29
+ end
30
+
31
+ private
32
+
33
+ def playbook_unfinished?
34
+ # The regexp is so that you can still refer to strings saying FIXME in your readme
35
+ # if you need to, by putting the phrase in backticks: `FIXME`
36
+ !File.read(LOCATION).match(/FIXME(?!`)/).nil?
37
+ end
38
+
39
+ def playbook_missing?
40
+ !File.exist?(LOCATION)
19
41
  end
20
42
  end
21
43
  end
@@ -0,0 +1,29 @@
1
+ # Playbook
2
+
3
+ This document details the failure modes of this service and mitigating steps that can be taken.
4
+
5
+ _FIXME: go through this document and fill out the sections marked FIXME._
6
+
7
+ ## Responsible humans
8
+
9
+ As a last resort, in an emergency, the following people have experience working with this service and have claimed responsibility for the running of this service. **If you have executed the plays below** and there is still a clear and present SEV-1 or greater issue, use the links below to get in touch:
10
+
11
+ - Responsible McPerson ([PagerDuty](https://deliveroo.pagerduty.com/users/FIXME), [Slack](https://deliveroo.slack.com/team/FIXME))
12
+
13
+ ## Owned models
14
+
15
+ - _FIXME_ - very brief description of the model, to aid in assessing what knock-on effects trouble with this service might have.
16
+
17
+ ## Failure modes
18
+
19
+ ### FIXME: an ability/functional area of the service
20
+
21
+ FIXME: A very brief high-level explanation of what this functional area does
22
+
23
+ #### 🚨 FIXME: a New Relic alert which might be raised - named so it can be searched for
24
+
25
+ FIXME: A description of the New Relic alert, what it means for the service, and what actions should be taken to remedy the situation.
26
+
27
+ #### 🚨 FIXME: an action which might need to be taken during an incident
28
+
29
+ FIXME: A description of how to perform this action, and any side-effects that the on-call engineer should be aware of.
@@ -3,10 +3,7 @@ require 'roo_on_rails/checks/github/branch_protection'
3
3
  require 'roo_on_rails/checks/heroku/app_exists'
4
4
  require 'roo_on_rails/checks/heroku/preboot_enabled'
5
5
  require 'roo_on_rails/checks/heroku/app_exists'
6
- require 'roo_on_rails/checks/sidekiq/settings'
7
6
  require 'roo_on_rails/checks/heroku/drains_metrics'
8
- require 'roo_on_rails/checks/documentation/playbook'
9
- require 'roo_on_rails/checks/google_oauth/initializer'
10
7
  require 'roo_on_rails/checks/papertrail/all'
11
8
 
12
9
  module RooOnRails
@@ -15,9 +12,6 @@ module RooOnRails
15
12
  requires GitHub::BranchProtection
16
13
  requires Heroku::DrainsMetrics
17
14
  requires Heroku::PrebootEnabled
18
- requires Sidekiq::Settings
19
- requires Documentation::Playbook
20
- requires GoogleOauth::Initializer
21
15
  requires Papertrail::All
22
16
 
23
17
  def call
@@ -12,7 +12,14 @@ module RooOnRails
12
12
  end
13
13
 
14
14
  def call
15
- fail! 'Custom Sidekiq settings found.' if File.exist?('config/sidekiq.yml')
15
+ if File.exist?('config/sidekiq.yml')
16
+ message = [
17
+ 'Custom Sidekiq settings found.',
18
+ ' Please see the Roo On Rails readme for more information.'
19
+ ].join("\n")
20
+
21
+ fail! message
22
+ end
16
23
  end
17
24
  end
18
25
  end
@@ -1,5 +1,4 @@
1
1
  require 'roo_on_rails/checks/base'
2
- require 'thor'
3
2
 
4
3
  module RooOnRails
5
4
  module Checks
@@ -20,11 +19,8 @@ module RooOnRails
20
19
  end
21
20
 
22
21
  def fix
23
- if File.exist?('Procfile')
24
- append_to_file 'Procfile', "\n#{WORKER_PROCFILE_LINE}"
25
- else
26
- create_file 'Procfile', WORKER_PROCFILE_LINE
27
- end
22
+ output = File.exist?('Procfile') ? "\n#{WORKER_PROCFILE_LINE}" : WORKER_PROCFILE_LINE
23
+ File.open('Procfile', 'a') { |f| f.write(output) }
28
24
  end
29
25
 
30
26
  def check_for_procfile
@@ -17,6 +17,14 @@ module RooOnRails
17
17
  enabled? 'GOOGLE_AUTH_ENABLED', default: false
18
18
  end
19
19
 
20
+ def google_auth_path_prefix
21
+ ENV.fetch('GOOGLE_AUTH_PATH_PREFIX')
22
+ end
23
+
24
+ def google_auth_controller
25
+ ENV.fetch('GOOGLE_AUTH_CONTROLLER')
26
+ end
27
+
20
28
  private
21
29
 
22
30
  ENABLED_PATTERN = /\A(YES|TRUE|ON|1)\Z/i
@@ -1,7 +1,9 @@
1
- GOOGLE_AUTH_ENABLED=true
1
+ GOOGLE_AUTH_ENABLED=false
2
2
  GOOGLE_AUTH_CLIENT_ID=''
3
3
  GOOGLE_AUTH_CLIENT_SECRET=''
4
4
  GOOGLE_AUTH_ALLOWED_DOMAINS=''
5
+ GOOGLE_AUTH_PATH_PREFIX=/auth
6
+ GOOGLE_AUTH_CONTROLLER=sessions
5
7
  NEW_RELIC_LOG=stdout
6
8
  NEW_RELIC_AGENT_ENABLED=true
7
9
  NEW_RELIC_MONITOR_MODE=true
@@ -2,21 +2,29 @@ require 'thor'
2
2
  require 'hashie'
3
3
  require 'roo_on_rails/checks/environment'
4
4
  require 'roo_on_rails/environment'
5
+ require 'roo_on_rails/checks/sidekiq/settings'
6
+ require 'roo_on_rails/checks/documentation/playbook'
5
7
 
6
8
  module RooOnRails
7
9
  class Harness
8
10
  include Thor::Shell
9
11
 
10
- def initialize(try_fix: false, context: nil)
12
+ def initialize(try_fix: false, context: Hashie::Mash.new, dry_run: false)
11
13
  @try_fix = try_fix
12
- @context = context || Hashie::Mash.new
14
+ @context = context
15
+ @dry_run = dry_run
13
16
  end
14
17
 
15
18
  def run
16
- [
17
- Checks::Environment.new(env: 'staging', fix: @try_fix, context: @context),
18
- Checks::Environment.new(env: 'production', fix: @try_fix, context: @context),
19
- ].each(&:run)
19
+ checks = [
20
+ Checks::Sidekiq::Settings.new(fix: @try_fix, context: @context, dry_run: @dry_run),
21
+ Checks::Documentation::Playbook.new(fix: @try_fix, context: @context, dry_run: @dry_run),
22
+ ]
23
+ environments.each do |env|
24
+ checks << Checks::Environment.new(env: env.strip, fix: @try_fix, context: @context, dry_run: @dry_run)
25
+ end
26
+
27
+ checks.each(&:run)
20
28
  self
21
29
  rescue Shell::CommandFailed
22
30
  say 'A command failed to run, aborting', %i[bold red]
@@ -25,5 +33,11 @@ module RooOnRails
25
33
  say 'A check failed, exiting', %i[bold red]
26
34
  exit 1
27
35
  end
36
+
37
+ private
38
+
39
+ def environments
40
+ ENV.fetch('ROO_ON_RAILS_ENVIRONMENTS', 'staging,production').split(',')
41
+ end
28
42
  end
29
43
  end
@@ -0,0 +1,75 @@
1
+ module RooOnRails
2
+ module Railties
3
+ class GoogleOAuth < Rails::Railtie
4
+ initializer 'roo_on_rails.google_auth.middleware' do |app|
5
+ _if_enabled do
6
+ _add_middleware(app)
7
+ end
8
+ end
9
+
10
+ initializer 'roo_on_rails.google_auth.routes', after: :set_routes_reloader_hook do |app|
11
+ _if_enabled do
12
+ _add_routes(app)
13
+ # support development mode on route changes (only works in Rails 5)
14
+ app.reloader.to_prepare { _add_routes(app) }
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def _if_enabled
21
+ return unless Config.google_auth_enabled?
22
+ if Rails::VERSION::MAJOR < 5
23
+ Rails.logger.warn 'The Google OAuth feature is not supported with Rails < 5'
24
+ return
25
+ end
26
+
27
+ yield
28
+ end
29
+
30
+ def _add_middleware(app)
31
+ $stderr.puts 'initializer roo_on_rails.google_auth.middleware'
32
+
33
+ require 'roo_on_rails/config'
34
+ require 'omniauth'
35
+ require 'omniauth-google-oauth2'
36
+ require 'active_support/core_ext/object/blank'
37
+
38
+ options = {
39
+ path_prefix: Config.google_auth_path_prefix,
40
+ prompt: 'consent',
41
+ # https://stackoverflow.com/questions/45271730/jwtinvalidissuererror-invalid-issuer-expected-accounts-google-com-received
42
+ # https://github.com/zquestz/omniauth-google-oauth2/issues/197
43
+ skip_jwt: true,
44
+ }
45
+
46
+ domain_list = ENV.fetch('GOOGLE_AUTH_ALLOWED_DOMAINS', '').split(',').reject(&:blank?)
47
+ options[:hd] = domain_list if domain_list.any?
48
+
49
+ app.config.middleware.use ::OmniAuth::Builder do
50
+ provider :google_oauth2,
51
+ ENV.fetch('GOOGLE_AUTH_CLIENT_ID'),
52
+ ENV.fetch('GOOGLE_AUTH_CLIENT_SECRET'),
53
+ options
54
+ end
55
+ end
56
+
57
+ def _add_routes(app)
58
+ $stderr.puts 'initializer roo_on_rails.google_auth.routes'
59
+
60
+ prefix = Config.google_auth_path_prefix
61
+ ctrl = Config.google_auth_controller
62
+
63
+ app.routes.disable_clear_and_finalize = true
64
+ app.routes.draw do
65
+ get "#{prefix}/google_oauth2", controller: ctrl, action: 'failure'
66
+ get "#{prefix}/google_oauth2/callback", controller: ctrl, action: 'create'
67
+ post "#{prefix}/google_oauth2/callback", controller: ctrl, action: 'create'
68
+ get "#{prefix}/failure", controller: ctrl, action: 'failure'
69
+ get "#{prefix}/logout", controller: ctrl, action: 'destroy'
70
+ end
71
+ app.routes.disable_clear_and_finalize = false
72
+ end
73
+ end
74
+ end
75
+ end
@@ -1,8 +1,14 @@
1
1
  require 'sidekiq/api'
2
2
  require 'roo_on_rails/sidekiq/queue_latency'
3
3
  require 'roo_on_rails/sidekiq/process_scaling'
4
+ require 'roo_on_rails/sidekiq/settings'
4
5
  require 'roo_on_rails/statsd'
5
6
 
7
+ # Reports Sidekiq queue metrics for queues configured within the current Sidekiq process
8
+ # i.e. queues returned by `RooOnRails::Sidekiq::Settings.queues`
9
+ # To enable reporting for custom queues, ensure your process is running the monitoring
10
+ # queue e.g. `SIDEKIQ_QUEUES="new-queue:5seconds,monitoring" bundle exec sidekiq`
11
+
6
12
  module RooOnRails
7
13
  module Sidekiq
8
14
  class MetricsWorker
@@ -12,7 +18,7 @@ module RooOnRails
12
18
 
13
19
  def perform
14
20
  RooOnRails.statsd.batch do |stats|
15
- queues = ::Sidekiq::Queue.all.map { |q| QueueLatency.new(q) }
21
+ queues = QueueLatency.queues
16
22
  queue_stats(stats, queues)
17
23
  process_stats(stats, queues)
18
24
  end
@@ -3,37 +3,38 @@ require 'sidekiq/api'
3
3
  module RooOnRails
4
4
  module Sidekiq
5
5
  class ProcessScaling
6
+ WORKER_INCREASE_THRESHOLD = ENV.fetch('WORKER_INCREASE_THRESHOLD', 0.5).to_f
7
+ WORKER_DECREASE_THRESHOLD = ENV.fetch('WORKER_DECREASE_THRESHOLD', 0.1).to_f
8
+ private_constant :WORKER_INCREASE_THRESHOLD
9
+ private_constant :WORKER_DECREASE_THRESHOLD
10
+
6
11
  def initialize(queue_latencies)
7
12
  @queue_latencies = queue_latencies
13
+ @queue_names = @queue_latencies.map(&:name)
8
14
  end
9
15
 
10
16
  def current_processes
11
- ::Sidekiq::ProcessSet.new.count
17
+ ::Sidekiq::ProcessSet.new.count do |process|
18
+ process['quiet'] == 'false' &&
19
+ @queue_names.any? do |queue_name|
20
+ process['queues'].include?(queue_name)
21
+ end
22
+ end
12
23
  end
13
24
 
14
25
  def max_normalised_latency
15
- @queue_latencies.any? ? @queue_latencies.map(&:normalised_latency).max : 0
26
+ @queue_latencies.any? ? @queue_latencies.map(&:normalised_latency).max.to_f : 0.0
16
27
  end
17
28
 
18
29
  def requested_processes
19
- if max_normalised_latency > increasing_latency
30
+ if max_normalised_latency > WORKER_INCREASE_THRESHOLD
20
31
  current_processes + 1
21
- elsif max_normalised_latency < decreasing_latency
32
+ elsif max_normalised_latency < WORKER_DECREASE_THRESHOLD
22
33
  [current_processes - 1, 1].max
23
34
  else
24
35
  current_processes
25
36
  end
26
37
  end
27
-
28
- protected
29
-
30
- def increasing_latency
31
- ENV.fetch('WORKER_INCREASE_THRESHOLD', 0.5).to_f
32
- end
33
-
34
- def decreasing_latency
35
- ENV.fetch('WORKER_DECREASE_THRESHOLD', 0.1).to_f
36
- end
37
38
  end
38
39
  end
39
40
  end
@@ -1,4 +1,6 @@
1
+ require 'active_support'
1
2
  require 'active_support/core_ext/numeric'
3
+ require 'roo_on_rails/sidekiq/settings'
2
4
 
3
5
  module RooOnRails
4
6
  module Sidekiq
@@ -8,23 +10,20 @@ module RooOnRails
8
10
  def_delegators :@queue, :size, :latency, :name
9
11
  attr_reader :queue
10
12
 
13
+ def self.queues
14
+ ::Sidekiq::Queue.all.each_with_object([]) do |q, array|
15
+ array << new(q) if Settings.queues.include?(q.name.to_s)
16
+ end
17
+ end
18
+
11
19
  def initialize(queue)
12
20
  @queue = queue
13
21
  end
14
22
 
15
23
  def normalised_latency
16
- metric = queue.latency.to_f / permitted_latency
17
- metric.round(3)
18
- end
19
-
20
- def permitted_latency
21
- prefix, number, unit = queue.name.partition(/[0-9]+/)
22
- case prefix
23
- when 'monitoring', 'realtime' then 10.seconds.to_i
24
- when 'default' then 1.day.to_i
25
- when 'within' then number.to_i.public_send(unit.to_sym).to_i
26
- else raise "Cannot determine permitted latency for queue #{queue.name}"
27
- end
24
+ permitted_latency = Settings.permitted_latency_values[queue.name]
25
+ return queue.latency.fdiv(permitted_latency).round(3) if permitted_latency
26
+ raise("Cannot determine permitted latency for queue #{queue.name}")
28
27
  end
29
28
  end
30
29
  end
@@ -1,20 +1,54 @@
1
+ require_relative './queue_latency'
2
+
1
3
  module RooOnRails
2
4
  module Sidekiq
3
5
  class Settings
4
- def self.queues
5
- %w(
6
- monitoring
7
- realtime
8
- within1minute
9
- within5minutes
10
- within30minutes
11
- within1hour
12
- within1day
13
- ).freeze
14
- end
6
+ DEFAULT_QUEUE_LATENCY_VALUES = {
7
+ 'monitoring' => 10.seconds.to_i,
8
+ 'realtime' => 10.seconds.to_i,
9
+ 'within1minute' => 1.minute.to_i,
10
+ 'within5minutes' => 5.minutes.to_i,
11
+ 'within30minutes' => 30.minutes.to_i,
12
+ 'within1hour' => 1.hour.to_i,
13
+ 'within1day' => 1.day.to_i,
14
+ 'default' => 1.day.to_i
15
+ }.freeze
16
+
17
+ class << self
18
+ def queues
19
+ @queues ||= permitted_latency_values.sort_by(&:last).map(&:first).freeze
20
+ end
21
+
22
+ def concurrency
23
+ ENV.fetch('SIDEKIQ_THREADS', 25)
24
+ end
25
+
26
+ def permitted_latency_values
27
+ @permitted_latency_values ||= ENV.key?('SIDEKIQ_QUEUES') ? env_queue_latency_values.freeze : DEFAULT_QUEUE_LATENCY_VALUES
28
+ end
29
+
30
+ private
15
31
 
16
- def self.concurrency
17
- ENV.fetch('SIDEKIQ_THREADS', 25)
32
+ def env_queue_latency_values
33
+ {}.tap do |hash|
34
+ ENV['SIDEKIQ_QUEUES'].split(',').each do |entry|
35
+ queue_entry = entry.strip
36
+ if DEFAULT_QUEUE_LATENCY_VALUES.key?(queue_entry)
37
+ queue_name = queue_entry
38
+ hash[queue_name] = DEFAULT_QUEUE_LATENCY_VALUES[queue_entry]
39
+ elsif queue_entry.match(/\Awithin\d+.+\z/)
40
+ _, number, unit = queue_entry.partition(/\d+/)
41
+ hash[queue_entry] = number.to_i.public_send(unit.strip).to_i
42
+ elsif queue_entry.include?(':')
43
+ queue_name, latency_info = queue_entry.split(':')
44
+ _, number, unit = latency_info.partition(/\d+/)
45
+ hash[queue_name] = number.to_i.public_send(unit.strip).to_i
46
+ else
47
+ hash[queue_entry] = nil
48
+ end
49
+ end
50
+ end
51
+ end
18
52
  end
19
53
  end
20
54
  end
@@ -6,10 +6,10 @@ module RooOnRails
6
6
  module Sidekiq
7
7
  # Returns stats on the current SLA performance of queues in a Sidekiq instance.
8
8
  #
9
- # Assumes workers are not bound to queues.
9
+ # Only returns stats for queues being processed by current Sidekiq process
10
10
  class SlaMetric
11
11
  def self.queue
12
- queues = ::Sidekiq::Queue.all.map { |q| QueueLatency.new(q) }
12
+ queues = QueueLatency.queues
13
13
  global_stats = ProcessScaling.new(queues)
14
14
  global_stats.requested_processes
15
15
  end
@@ -1,3 +1,3 @@
1
1
  module RooOnRails
2
- VERSION = '1.7.0'.freeze
2
+ VERSION = '1.8.0'.freeze
3
3
  end
data/lib/roo_on_rails.rb CHANGED
@@ -11,5 +11,5 @@ if defined?(Rails)
11
11
  require 'roo_on_rails/railties/http'
12
12
  require 'roo_on_rails/railties/sidekiq'
13
13
  require 'roo_on_rails/railties/rake_tasks'
14
- require 'roo_on_rails/railties/google_auth'
14
+ require 'roo_on_rails/railties/google_oauth'
15
15
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roo_on_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julien Letessier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-07-18 00:00:00.000000000 Z
11
+ date: 2017-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dotenv-rails
@@ -358,6 +358,7 @@ files:
358
358
  - Gemfile
359
359
  - Guardfile
360
360
  - LICENSE.txt
361
+ - README.google_oauth2.md
361
362
  - README.md
362
363
  - Rakefile
363
364
  - appraise
@@ -377,13 +378,12 @@ files:
377
378
  - lib/roo_on_rails/checks.rb
378
379
  - lib/roo_on_rails/checks/base.rb
379
380
  - lib/roo_on_rails/checks/documentation/playbook.rb
381
+ - lib/roo_on_rails/checks/documentation/playbook_template.md
380
382
  - lib/roo_on_rails/checks/env_specific.rb
381
383
  - lib/roo_on_rails/checks/environment.rb
382
384
  - lib/roo_on_rails/checks/git/origin.rb
383
385
  - lib/roo_on_rails/checks/github/branch_protection.rb
384
386
  - lib/roo_on_rails/checks/github/token.rb
385
- - lib/roo_on_rails/checks/google_oauth/_template.rb
386
- - lib/roo_on_rails/checks/google_oauth/initializer.rb
387
387
  - lib/roo_on_rails/checks/helpers.rb
388
388
  - lib/roo_on_rails/checks/heroku/app_exists.rb
389
389
  - lib/roo_on_rails/checks/heroku/drains_metrics.rb
@@ -408,11 +408,10 @@ files:
408
408
  - lib/roo_on_rails/harness.rb
409
409
  - lib/roo_on_rails/logfmt.rb
410
410
  - lib/roo_on_rails/papertrail_client.rb
411
- - lib/roo_on_rails/rack/google_oauth.rb
412
411
  - lib/roo_on_rails/rack/safe_timeouts.rb
413
412
  - lib/roo_on_rails/railtie.rb
414
413
  - lib/roo_on_rails/railties/database.rb
415
- - lib/roo_on_rails/railties/google_auth.rb
414
+ - lib/roo_on_rails/railties/google_oauth.rb
416
415
  - lib/roo_on_rails/railties/http.rb
417
416
  - lib/roo_on_rails/railties/new_relic.rb
418
417
  - lib/roo_on_rails/railties/rake_tasks.rb
@@ -1,49 +0,0 @@
1
- # Google Oauth initializer, generated by RooOnRails
2
-
3
- require 'roo_on_rails/rack/google_oauth'
4
-
5
- Rails.application.config.middleware.use RooOnRails::Rack::GoogleOauth do |env|
6
- # This is your auth strategy.
7
- # Here you're supposed to do something with the OAuth payload and
8
- # return a valid Rack response.
9
-
10
- # A simple and insecure example:
11
- #
12
- require 'digest/md5'
13
- auth_data = env['omniauth.auth']
14
- naive_token = Digest::MD5.hexdigest(auth_data.info.email.downcase)
15
- expires_in = Time.current + 60 * 60 * 24
16
- headers = { 'Location' => '/' }
17
- Rack::Utils.set_cookie_header!(headers, 'naive_auth_cookie', {
18
- value: naive_token, expires: expires_in, path: '/'
19
- })
20
- [302, headers, ['You are being redirecred to /']]
21
-
22
- # You can also hand it over to a Rails controller action, where the
23
- # OAuth payload will be available in `request.env['omniauth.auth']`.
24
- # If you do this, the controller will take care of returning a valid
25
- # response for Rack.
26
- #
27
- # This is the recommenced approach as it makes it easier to use
28
- # Rails encrypted cookies and other security features.
29
- #
30
- # For example:
31
- # MyAuthController.action(:login).call(env)
32
- end
33
-
34
- # What to do in case of failure.
35
- # Must be a 302 redirect.
36
- # It can invoke a Rails controller action
37
- #
38
- OmniAuth.config.on_failure = proc do |env|
39
- error = env['omniauth.error'] # e.g. #<OmniAuth::Strategies::OAuth2::CallbackError: OmniAuth::Strategies::OAuth2::CallbackError>
40
- details = error.message # e.g. "invalid_hd | Invalid Hosted Domain"
41
- error_type = env['omniauth.error.type'] # e.g. :invalid_credentials
42
-
43
- Rails.logger.info("[RooOnRails] Login failed (#{error_type}): #{details}")
44
-
45
- # To use a rails controller;
46
- # MyAuthController.action(:login_failed).call(env)
47
-
48
- [302, { 'Location' => '/' }, ['']]
49
- end
@@ -1,43 +0,0 @@
1
- require 'roo_on_rails/config'
2
- require 'roo_on_rails/checks/base'
3
- require 'fileutils'
4
-
5
- module RooOnRails
6
- module Checks
7
- module GoogleOauth
8
- class Initializer < Base
9
- LOCATION = 'config/initializers/google_oauth.rb'.freeze
10
-
11
- def intro
12
- 'Google Oauth protection'
13
- end
14
-
15
- def call
16
- if RooOnRails::Config.google_auth_enabled?
17
- check_initializer
18
- else
19
- pass 'Google Oauth is not enabled. Doing nothing'
20
- end
21
- end
22
-
23
- def fix
24
- FileUtils.cp(template, LOCATION)
25
- end
26
-
27
- private
28
-
29
- def check_initializer
30
- if File.exist? LOCATION
31
- pass 'Google Oauth initializer is present. Doing nothing.'
32
- else
33
- fail! 'Google Oauth is enabled but the initializer is missing.'
34
- end
35
- end
36
-
37
- def template
38
- File.join(__dir__, '_template.rb')
39
- end
40
- end
41
- end
42
- end
43
- end
@@ -1,34 +0,0 @@
1
- require 'rack'
2
-
3
- module RooOnRails
4
- module Rack
5
- class GoogleOauth
6
- OAUTH_CALLBACK = '/auth/google_oauth2/callback'.freeze
7
-
8
- def initialize(app, *args, &block)
9
- @app = app
10
- @args = args
11
- @strategy = block
12
- end
13
-
14
- def call(env)
15
- if is_oauth_callback?(env)
16
- @strategy.call(env)
17
- else
18
- send_to_upstream(env)
19
- end
20
- end
21
-
22
- private
23
-
24
- def send_to_upstream(env)
25
- @app.call(env)
26
- end
27
-
28
- def is_oauth_callback?(env)
29
- request = ::Rack::Request.new(env)
30
- request.fullpath.start_with?(OAUTH_CALLBACK)
31
- end
32
- end
33
- end
34
- end
@@ -1,36 +0,0 @@
1
- require 'roo_on_rails/config'
2
- require 'omniauth'
3
- require 'omniauth-google-oauth2'
4
- require 'active_support/core_ext/object/blank'
5
-
6
- module RooOnRails
7
- module Railties
8
- class GoogleAuth < Rails::Railtie
9
- initializer 'roo_on_rails.google_auth' do |app|
10
- if RooOnRails::Config.google_auth_enabled?
11
- $stderr.puts 'initializer roo_on_rails.google_auth'
12
-
13
- google_oauth2_client_id = ENV.fetch('GOOGLE_AUTH_CLIENT_ID')
14
- google_oauth2_client_secret = ENV.fetch('GOOGLE_AUTH_CLIENT_SECRET')
15
-
16
- options = {
17
- path_prefix: '/auth',
18
- prompt: 'consent',
19
- }
20
-
21
- domain_list = ENV.fetch('GOOGLE_AUTH_ALLOWED_DOMAINS', '').split(',').reject(&:blank?)
22
- options[:hd] = domain_list if domain_list.any?
23
-
24
- app.config.middleware.use ::OmniAuth::Builder do
25
- provider :google_oauth2,
26
- google_oauth2_client_id,
27
- google_oauth2_client_secret,
28
- options
29
- end
30
- else
31
- $stderr.puts 'skipping initializer roo_on_rails.google_auth'
32
- end
33
- end
34
- end
35
- end
36
- end