roo_on_rails 1.7.0 → 1.8.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
  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