web47core 2.0.1 → 2.2.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +111 -41
  3. data/app/controllers/status_controller.rb +8 -3
  4. data/app/helpers/core_form_helper.rb +6 -6
  5. data/app/helpers/core_helper.rb +1 -1
  6. data/app/helpers/core_link_helper.rb +47 -8
  7. data/app/helpers/core_nav_bar_helper.rb +5 -4
  8. data/app/helpers/core_table_helper.rb +80 -0
  9. data/app/helpers/model_modal_helper.rb +3 -3
  10. data/app/views/common/_create_actions.html.haml +12 -0
  11. data/app/views/common/_update_actions.html.haml +10 -0
  12. data/app/views/cron/_edit.html.haml +15 -17
  13. data/app/views/cron/_index.html.haml +74 -67
  14. data/app/views/delayed_job_metrics/_index.html.haml +27 -0
  15. data/app/views/delayed_job_metrics/index.html.haml +1 -0
  16. data/app/views/delayed_job_workers/_index.html.haml +27 -0
  17. data/app/views/delayed_job_workers/index.html.haml +1 -0
  18. data/app/views/delayed_jobs/_index.html.haml +47 -52
  19. data/app/views/delayed_jobs/_show.html.haml +15 -13
  20. data/app/views/system_configurations/_edit.html.haml +14 -9
  21. data/app/views/system_configurations/_show.html.haml +18 -12
  22. data/config/brakeman.ignore +26 -0
  23. data/config/brakeman.yml +2 -0
  24. data/config/locales/en.yml +21 -3
  25. data/lib/app/controllers/concerns/core_delayed_job_metrics_controller.rb +35 -0
  26. data/lib/app/controllers/concerns/core_delayed_job_workers_controller.rb +35 -0
  27. data/lib/app/controllers/concerns/core_delayed_jobs_controller.rb +2 -3
  28. data/lib/app/jobs/cron/command.rb +1 -4
  29. data/lib/app/jobs/cron/record_delayed_job_metrics.rb +25 -0
  30. data/lib/app/jobs/cron/restart_orphaned_delayed_jobs.rb +44 -0
  31. data/lib/app/jobs/cron/server.rb +32 -15
  32. data/lib/app/jobs/cron/switchboard_sync_configuration.rb +2 -0
  33. data/lib/app/jobs/cron/switchboard_sync_models.rb +2 -0
  34. data/lib/app/jobs/cron/trim_collection.rb +1 -1
  35. data/lib/app/jobs/cron/trim_delayed_job_metrics.rb +29 -0
  36. data/lib/app/jobs/cron/trim_delayed_job_workers.rb +39 -0
  37. data/lib/app/jobs/cron/trim_failed_delayed_jobs.rb +2 -0
  38. data/lib/app/models/api_token.rb +9 -0
  39. data/lib/app/models/command_job.rb +12 -11
  40. data/lib/app/models/concerns/api_tokenable.rb +38 -0
  41. data/lib/app/models/concerns/aws_configuration.rb +65 -0
  42. data/lib/app/models/concerns/cdn_url.rb +7 -0
  43. data/lib/app/models/concerns/core_smtp_configuration.rb +65 -0
  44. data/lib/app/models/concerns/core_system_configuration.rb +18 -201
  45. data/lib/app/models/concerns/delayed_job_configuration.rb +24 -0
  46. data/lib/app/models/concerns/email_able.rb +6 -2
  47. data/lib/app/models/concerns/google_sso_configuration.rb +32 -0
  48. data/lib/app/models/concerns/search_able.rb +16 -0
  49. data/lib/app/models/concerns/server_process_able.rb +69 -0
  50. data/lib/app/models/concerns/slack_configuration.rb +38 -0
  51. data/lib/app/models/concerns/standard_model.rb +5 -2
  52. data/lib/app/models/concerns/switchboard_configuration.rb +43 -0
  53. data/lib/app/models/concerns/twilio_configuration.rb +37 -0
  54. data/lib/app/models/concerns/zendesk_configuration.rb +92 -0
  55. data/lib/app/models/{delayed_job.rb → delayed/backend/delayed_job.rb} +41 -0
  56. data/lib/app/models/delayed/jobs/metric.rb +61 -0
  57. data/lib/app/models/delayed/jobs/run.rb +40 -0
  58. data/lib/app/models/delayed/jobs/worker.rb +43 -0
  59. data/lib/app/models/delayed/plugins/time_keeper.rb +33 -0
  60. data/lib/app/models/delayed/worker.rb +24 -0
  61. data/lib/app/models/email_notification.rb +2 -1
  62. data/lib/app/models/email_template.rb +5 -6
  63. data/lib/app/models/notification.rb +12 -2
  64. data/lib/app/models/sms_notification.rb +9 -6
  65. data/lib/app/models/template.rb +12 -12
  66. data/lib/web47core/version.rb +1 -1
  67. data/lib/web47core.rb +33 -9
  68. metadata +114 -69
@@ -0,0 +1,44 @@
1
+ #
2
+ # Run in cron
3
+ #
4
+ module Cron
5
+ #
6
+ # Cycle through all members and tell them to sync with with switchboard
7
+ #
8
+ class RestartOrphanedDelayedJobs < Job
9
+ cron_tab_entry :hourly
10
+
11
+ #
12
+ # Only run when we have background jobs and we have the time keeper plugin installed
13
+ #
14
+ def self.valid_environment?
15
+ Delayed::Worker.plugins.include?(Delayed::Plugins::TimeKeeper) &&
16
+ SystemConfiguration.delayed_job_restart_orphaned? &&
17
+ Delayed::Backend::Mongoid::Job.count.positive?
18
+ rescue StandardError
19
+ false
20
+ end
21
+
22
+ #
23
+ # Cycle through delayed jobs, looking for running jobs
24
+ # skip if we don't have any records or low sample set
25
+ # skip if the allowed time is less than allowed by the job
26
+ # look to see if have a worker associated with the delayed job
27
+ # If we dont have a worker or the worker is dead, restart the job
28
+ #
29
+ def execute
30
+ Delayed::Backend::Mongoid::Job.each do |delayed_job|
31
+ next unless delayed_job.running?
32
+
33
+ metric = Delayed::Jobs::Metric.where(name: delayed_job.display_name).first
34
+ next if metric.blank? || metric.count < 30 # not enough data to make a call
35
+
36
+ run_time = Time.now.utc - delayed_job.locked_at
37
+ next if run_time < metric.max_allowed_seconds # still within parameters
38
+
39
+ worker = delayed_job.worker
40
+ delayed_job.resubmit if worker.blank? || worker.dead?
41
+ end
42
+ end
43
+ end
44
+ end
@@ -6,6 +6,7 @@ module Cron
6
6
  #
7
7
  class Server
8
8
  include StandardModel
9
+ include ServerProcessAble
9
10
  #
10
11
  # Constants
11
12
  #
@@ -15,20 +16,19 @@ module Cron
15
16
  #
16
17
  # Fields
17
18
  #
18
- field :host_name, type: String
19
- field :pid, type: Integer
20
19
  field :desired_server_count, type: Integer, default: 0
21
20
  field :current_server_count, type: Integer, default: 0
22
- field :last_check_in_at, type: Time, default: Time.now.utc
23
21
  field :state, type: String, default: STATE_SECONDARY
24
22
  #
25
23
  # Validations
26
24
  #
27
- validates :host_name, presence: true
28
- validates :pid, presence: true
29
- validates :last_check_in_at, presence: true
30
25
  validates :state, inclusion: { in: ALL_STATES }
31
26
  validate :high_lander
27
+ #
28
+ # Callback
29
+ #
30
+ before_validation :ensure_last_check_in
31
+
32
32
  #
33
33
  # Go through the logic once a minute
34
34
  #
@@ -89,7 +89,7 @@ module Cron
89
89
  # Warm up a server on the next evaluation
90
90
  #
91
91
  def self.warm_up_server
92
- return unless SystemConfiguration.aws_auto_scaling_configured?
92
+ return unless auto_scaling_configured?
93
93
 
94
94
  primary_server.auto_scale([primary_server.desired_server_count + 1, 10].min)
95
95
  end
@@ -148,18 +148,11 @@ module Cron
148
148
  !alive?
149
149
  end
150
150
 
151
- #
152
- # Perform a check in for the server
153
- #
154
- def check_in
155
- set({ last_check_in_at: Time.now.utc })
156
- end
157
-
158
151
  #
159
152
  # Auto scale environment
160
153
  #
161
154
  def check_auto_scale
162
- return unless SystemConfiguration.aws_auto_scaling_configured?
155
+ return unless auto_scaling_configured?
163
156
 
164
157
  if delayed_jobs_count.eql?(0)
165
158
  handle_zero_job_count
@@ -181,6 +174,24 @@ module Cron
181
174
  @sys_config ||= SystemConfiguration.configuration
182
175
  end
183
176
 
177
+ #
178
+ # Test if autoscaling is configured, return false if there is an error
179
+ #
180
+ def auto_scaling_configured?
181
+ @auto_scaling_configured ||= sys_config.aws_auto_scaling_configured?
182
+ rescue StandardError
183
+ false
184
+ end
185
+
186
+ #
187
+ # Test if autoscaling is configured, return false if there is an error
188
+ #
189
+ def self.auto_scaling_configured?
190
+ SystemConfiguration.aws_auto_scaling_configured?
191
+ rescue StandardError
192
+ false
193
+ end
194
+
184
195
  #
185
196
  # Returns the AutoScalingGroup associated with the account
186
197
  #
@@ -258,6 +269,8 @@ module Cron
258
269
  client.update_auto_scaling_group(auto_scaling_group_name: sys_config.aws_auto_scaling_group_name,
259
270
  min_size: desired_count,
260
271
  desired_capacity: desired_count)
272
+ rescue StandardError => error
273
+ App47Logger.log_error "Unable to set auto scaler to #{desired_count}", error
261
274
  end
262
275
 
263
276
  #
@@ -283,5 +296,9 @@ module Cron
283
296
  def inactive_count
284
297
  desired_server_count
285
298
  end
299
+
300
+ def ensure_last_check_in
301
+ self.last_check_in_at ||= Time.now.utc
302
+ end
286
303
  end
287
304
  end
@@ -13,6 +13,8 @@ module Cron
13
13
  #
14
14
  def self.valid_environment?
15
15
  SystemConfiguration.switchboard_configured?
16
+ rescue StandardError
17
+ false
16
18
  end
17
19
 
18
20
  #
@@ -13,6 +13,8 @@ module Cron
13
13
  #
14
14
  def self.valid_environment?
15
15
  SystemConfiguration.switchboard_configured? && Web47core::Config.switchboard_able_models.present?
16
+ rescue StandardError
17
+ false
16
18
  end
17
19
 
18
20
  #
@@ -57,7 +57,7 @@ module Cron
57
57
  # Allowed time the amount of time allowed to exists before deleting
58
58
  #
59
59
  def allowed_time
60
- 30.days.ago
60
+ 30.days.ago.utc
61
61
  end
62
62
  end
63
63
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cron
4
+ #
5
+ # Clean up Audit Logs that have not been updated in 90 days
6
+ #
7
+ class TrimDelayedJobMetrics < TrimCollection
8
+ #
9
+ # Fetch each Audit Log and delete it if hasn't been updated in 90 days
10
+ #
11
+ def collection
12
+ Delayed::Jobs::Metric.all
13
+ end
14
+
15
+ #
16
+ # Return which field to use for comparison when trimming objects
17
+ #
18
+ def comparison_field
19
+ :last_run_at
20
+ end
21
+
22
+ #
23
+ # Allowed time the amount of time allowed to exists before deleting
24
+ #
25
+ def allowed_time
26
+ 12.months.ago.utc
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cron
4
+ #
5
+ # Clean up Audit Logs that have not been updated in 90 days
6
+ #
7
+ class TrimDelayedJobWorkers < TrimCollection
8
+ #
9
+ # Fetch each Audit Log and delete it if hasn't been updated in 90 days
10
+ #
11
+ def collection
12
+ Delayed::Jobs::Worker.all
13
+ end
14
+
15
+ #
16
+ # Check if the cron job server hasn't reported in a while
17
+ #
18
+ def archive?(worker)
19
+ super && worker.runs.blank?
20
+ rescue StandardError => error
21
+ App47Logger.log_warn "Unable to archive item #{worker.inspect}", error
22
+ false
23
+ end
24
+
25
+ #
26
+ # Return which field to use for comparison when trimming objects
27
+ #
28
+ def comparison_field
29
+ :last_check_in_at
30
+ end
31
+
32
+ #
33
+ # Allowed time the amount of time allowed to exists before deleting
34
+ #
35
+ def allowed_time
36
+ 2.days.ago.utc
37
+ end
38
+ end
39
+ end
@@ -35,6 +35,8 @@ module Cron
35
35
  #
36
36
  def self.valid_environment?
37
37
  SystemConfiguration.slack_api_url.present?
38
+ rescue StandardError
39
+ false
38
40
  end
39
41
  end
40
42
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # A specific API token to be used as part of a collection of tokens
5
+ #
6
+ class ApiToken
7
+ include StandardModel
8
+ include ApiTokenable
9
+ end
@@ -110,7 +110,7 @@ class CommandJob
110
110
  end
111
111
 
112
112
  def failure_or_cancelled?
113
- job_state?([STATE_FAIL, STATE_CANCELLED], true)
113
+ job_state?([STATE_FAIL, STATE_CANCELLED], default_state: true)
114
114
  end
115
115
 
116
116
  #
@@ -118,11 +118,11 @@ class CommandJob
118
118
  # state. If there is a match, then return true, otherwise return false.
119
119
  # If there is an error, return the default.
120
120
  #
121
- def job_state?(states, default = false)
121
+ def job_state?(states, default_state: false)
122
122
  states.is_a?(Array) ? states.include?(state) : states.eql?(state)
123
123
  rescue StandardError => error
124
- App47Logger.log_warn "Unable to check job failed or cancelled #{self.inspect}", error
125
- default
124
+ App47Logger.log_warn "Unable to check job failed or cancelled #{inspect}", error
125
+ default_state
126
126
  end
127
127
 
128
128
  #
@@ -323,8 +323,9 @@ class CommandJob
323
323
  else
324
324
  logs.create!(dir: dir, command: command, message: output)
325
325
  end
326
- check_for_text(output, options[:error_texts], true)
327
- check_for_text(output, options[:required_texts], false)
326
+ options[:output_limit] ||= -1
327
+ check_for_text(output, options[:error_texts], output_limit: options[:output_limit])
328
+ check_for_text(output, options[:required_texts], inclusive_check: false, output_limit: options[:output_limit])
328
329
  output
329
330
  end
330
331
 
@@ -343,16 +344,17 @@ class CommandJob
343
344
 
344
345
  #
345
346
  # Check if any occurrences were found (or not found)
347
+ # For most command jobs, we want to see the full output. -1 accomplishes this
346
348
  #
347
- def check_for_text(output, texts = [], inclusive = true)
349
+ def check_for_text(output, texts = [], inclusive_check: true, output_limit: -1)
348
350
  return if texts.blank?
349
351
 
350
352
  texts = [texts] if texts.is_a?(String)
351
353
  texts.each do |text|
352
- if inclusive
353
- raise "Error: found text (#{text}) - #{output}" if output.match?(/#{text}/)
354
+ if inclusive_check
355
+ raise "Error: found text (#{text}) - #{output[0...output_limit]}" if output.match?(/#{text}/)
354
356
  else
355
- raise "Error: missing text (#{text}) - #{output}" unless output.match?(/#{text}/)
357
+ raise "Error: missing text (#{text}) - #{output[0...output_limit]}" unless output.match?(/#{text}/)
356
358
  end
357
359
  end
358
360
  end
@@ -370,5 +372,4 @@ class CommandJob
370
372
  def sort_fields
371
373
  %i[created_at]
372
374
  end
373
-
374
375
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # API tokenable, support for the api token in user, but also may be applied elsewhere
5
+ #
6
+ module ApiTokenable
7
+ extend ActiveSupport::Concern
8
+
9
+ def self.included(base)
10
+ base.class_eval do
11
+ # store api token
12
+ field :api_token, type: String
13
+ # if the api token should be reset
14
+ field :reset_api_token, type: Boolean, default: true
15
+ field :last_authenticated_at, type: Time
16
+ field :last_authenticated_ip, type: String
17
+ # call back to reset the api token.
18
+ before_save :assign_api_token, if: :reset_api_token
19
+ # set the index on api token
20
+ index({ api_token: 1 }, background: true)
21
+
22
+ def cycle_api_token
23
+ self.set api_token: SecureRandom.urlsafe_base64
24
+ api_token
25
+ end
26
+
27
+ private
28
+
29
+ #
30
+ # set the api token
31
+ #
32
+ def assign_api_token
33
+ self.reset_api_token = false
34
+ self.api_token = SecureRandom.urlsafe_base64
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # AWS Configuration
5
+ #
6
+ module AwsConfiguration
7
+ extend ActiveSupport::Concern
8
+
9
+ def self.included(base)
10
+ base.class_eval do
11
+ #
12
+ # Fields
13
+ #
14
+ field :aws_region, type: String
15
+ field :aws_access_key_id, type: String
16
+ field :aws_secret_access_key, type: String
17
+ field :aws_auto_scaling_group_name, type: String
18
+ end
19
+ end
20
+
21
+ #
22
+ # Make sure the password doesn't get blanked out on an update
23
+ #
24
+ def secure_fields
25
+ super + %i[aws_secret_access_key]
26
+ end
27
+
28
+ #
29
+ # Determine if AWS is configured
30
+ #
31
+ def aws_configured?
32
+ [aws_region.present?, aws_access_key_id.present?, aws_secret_access_key.present?].all?
33
+ end
34
+
35
+ #
36
+ # Determine if auto scaling group is configured
37
+ #
38
+ def aws_auto_scaling_configured?
39
+ aws_configured? && aws_auto_scaling_group_name.present?
40
+ end
41
+
42
+ #
43
+ # AWS client.
44
+ #
45
+ def aws_ec2_client
46
+ return nil unless aws_configured?
47
+
48
+ @aws_ec2_client ||= Aws::EC2::Client.new(region: aws_region,
49
+ credentials: Aws::Credentials.new(aws_access_key_id,
50
+ aws_secret_access_key))
51
+ end
52
+
53
+ #
54
+ # S3 Client
55
+ #
56
+ def aws_s3_client
57
+ return nil unless aws_configured?
58
+
59
+ # We want this to remake itself each time because it is possible that the
60
+ # => user would change the access keys in between actions. Huh?
61
+ @aws_s3_client ||= Aws::S3::Client.new(region: aws_vault_bucket_region,
62
+ access_key_id: aws_access_key_id,
63
+ secret_access_key: aws_secret_access_key)
64
+ end
65
+ end
@@ -17,6 +17,13 @@
17
17
  #
18
18
  module CdnUrl
19
19
  extend ActiveSupport::Concern
20
+ #
21
+ # Constants
22
+ #
23
+ STYLE_S3_FILE_PATH = ':class/:attachment/:id/:style.:extension' unless defined? STYLE_S3_FILE_PATH
24
+ STYLE_FILE_PATH = 'public/system/:class/:attachment/:id/:style.:extension' unless defined? STYLE_FILE_PATH
25
+ STYLE_S3_FILE_URL = ':s3_domain_url' unless defined? STYLE_S3_FILE_URL
26
+ STYLE_FILE_URL = ':rails_root/public/system/:class/:attachment/:id/:style.:extension' unless defined? STYLE_FILE_URL
20
27
 
21
28
  def method_missing(method, *args)
22
29
  if method.to_s.start_with? 'cdn_'
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # SMTP Configuration
5
+ #
6
+ module CoreSmtpConfiguration
7
+ extend ActiveSupport::Concern
8
+
9
+ def self.included(base)
10
+ base.class_eval do
11
+ #
12
+ # Fields
13
+ #
14
+ field :default_email, type: String, default: 'support@app47.com'
15
+ field :support_email, type: String, default: 'support@app47.com'
16
+ field :smtp_name, type: String
17
+ field :smtp_address, type: String
18
+ field :smtp_domain, type: String
19
+ field :smtp_port, type: Integer, default: 587
20
+ field :smtp_user_name, type: String
21
+ field :smtp_password, type: String
22
+ field :smtp_enable_starttls_auto, type: Boolean, default: false
23
+ field :mailgun_api_key, type: String
24
+ field :email_notification_ttl, type: Integer, default: 180
25
+ end
26
+ base.extend SmtpClassMethods
27
+ end
28
+
29
+ #
30
+ # Class methods for smtp configuration
31
+ #
32
+ module SmtpClassMethods
33
+ def smtp_configuration
34
+ output = {}
35
+ config = configuration
36
+ fields = %w[name address domain port user_name password enable_starttls_auto]
37
+ fields.each do |field|
38
+ field_name = "smtp_#{field}".to_sym
39
+ output[field.to_sym] = config.send(field_name)
40
+ end
41
+ output
42
+ end
43
+ end
44
+
45
+ #
46
+ # Make sure the password doesn't get blanked out on an update
47
+ #
48
+ def secure_fields
49
+ super + %i[smtp_password mailgun_api_key]
50
+ end
51
+
52
+ #
53
+ # Determine if SMTP is configured
54
+ #
55
+ def smtp_configured?
56
+ smtp_name.present? && smtp_address.present? && smtp_domain.present?
57
+ end
58
+
59
+ #
60
+ # Determine if mailgun is configured
61
+ #
62
+ def mail_gun_configured?
63
+ smtp_configured? && mailgun_api_key.present?
64
+ end
65
+ end