maintenance_on_steroids 0.2.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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +267 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +948 -0
  5. data/app/controllers/maintenance_on_steroids/application_controller.rb +66 -0
  6. data/app/controllers/maintenance_on_steroids/dashboard_controller.rb +18 -0
  7. data/app/controllers/maintenance_on_steroids/jobs_controller.rb +59 -0
  8. data/app/controllers/maintenance_on_steroids/runs_controller.rb +223 -0
  9. data/app/jobs/maintenance_on_steroids/run_job.rb +292 -0
  10. data/app/models/maintenance_on_steroids/application_record.rb +6 -0
  11. data/app/models/maintenance_on_steroids/artifact.rb +255 -0
  12. data/app/models/maintenance_on_steroids/run.rb +310 -0
  13. data/app/views/layouts/maintenance_on_steroids/application.html.erb +48 -0
  14. data/app/views/maintenance_on_steroids/dashboard/index.html.erb +163 -0
  15. data/app/views/maintenance_on_steroids/jobs/index.html.erb +35 -0
  16. data/app/views/maintenance_on_steroids/jobs/show.html.erb +107 -0
  17. data/app/views/maintenance_on_steroids/jobs/source.html.erb +79 -0
  18. data/app/views/maintenance_on_steroids/runs/new.html.erb +98 -0
  19. data/app/views/maintenance_on_steroids/runs/show.html.erb +410 -0
  20. data/app/views/maintenance_on_steroids/shared/_auto_refresh.html.erb +85 -0
  21. data/app/views/maintenance_on_steroids/shared/_javascript.html.erb +16 -0
  22. data/app/views/maintenance_on_steroids/shared/_pager.html.erb +20 -0
  23. data/app/views/maintenance_on_steroids/shared/_styles.html.erb +649 -0
  24. data/app/views/maintenance_on_steroids/shared/_task_list_item.html.erb +31 -0
  25. data/config/routes.rb +22 -0
  26. data/lib/generators/maintenance_on_steroids/install/install_generator.rb +56 -0
  27. data/lib/generators/maintenance_on_steroids/install/templates/create_maintenance_on_steroids_tables.rb.erb +56 -0
  28. data/lib/generators/maintenance_on_steroids/install/templates/initializer.rb +42 -0
  29. data/lib/generators/maintenance_on_steroids/job/job_generator.rb +17 -0
  30. data/lib/generators/maintenance_on_steroids/job/templates/job.rb.erb +30 -0
  31. data/lib/maintenance_on_steroids/about_dsl.rb +51 -0
  32. data/lib/maintenance_on_steroids/artifact_dsl.rb +82 -0
  33. data/lib/maintenance_on_steroids/artifacts_proxy.rb +205 -0
  34. data/lib/maintenance_on_steroids/callbacks_dsl.rb +61 -0
  35. data/lib/maintenance_on_steroids/csv_artifact.rb +88 -0
  36. data/lib/maintenance_on_steroids/engine.rb +26 -0
  37. data/lib/maintenance_on_steroids/form_dsl.rb +63 -0
  38. data/lib/maintenance_on_steroids/instrumentation.rb +33 -0
  39. data/lib/maintenance_on_steroids/job_dsl.rb +61 -0
  40. data/lib/maintenance_on_steroids/job_registry.rb +83 -0
  41. data/lib/maintenance_on_steroids/jsonb_artifact.rb +39 -0
  42. data/lib/maintenance_on_steroids/params_proxy.rb +93 -0
  43. data/lib/maintenance_on_steroids/task.rb +109 -0
  44. data/lib/maintenance_on_steroids/text_artifact.rb +74 -0
  45. data/lib/maintenance_on_steroids/version.rb +3 -0
  46. data/lib/maintenance_on_steroids.rb +187 -0
  47. metadata +125 -0
@@ -0,0 +1,187 @@
1
+ require_relative "maintenance_on_steroids/version"
2
+ require_relative "maintenance_on_steroids/engine"
3
+ require_relative "maintenance_on_steroids/instrumentation"
4
+ require_relative "maintenance_on_steroids/job_registry"
5
+ require_relative "maintenance_on_steroids/form_dsl"
6
+ require_relative "maintenance_on_steroids/artifact_dsl"
7
+ require_relative "maintenance_on_steroids/job_dsl"
8
+ require_relative "maintenance_on_steroids/about_dsl"
9
+ require_relative "maintenance_on_steroids/callbacks_dsl"
10
+ require_relative "maintenance_on_steroids/params_proxy"
11
+ require_relative "maintenance_on_steroids/jsonb_artifact"
12
+ require_relative "maintenance_on_steroids/text_artifact"
13
+ require_relative "maintenance_on_steroids/csv_artifact"
14
+ require_relative "maintenance_on_steroids/artifacts_proxy"
15
+ require_relative "maintenance_on_steroids/task"
16
+
17
+ module MaintenanceOnSteroids
18
+ # Base controller class for the engine's controllers (resolved at load time).
19
+ # Set this in an initializer, before the engine's controllers are loaded.
20
+ mattr_accessor :parent_controller, default: "ActionController::Base"
21
+
22
+ # Maximum allowed size (in bytes) for file inputs uploaded when starting a run.
23
+ # Uploads are read into memory and stored in the artifacts table.
24
+ mattr_accessor :max_upload_size, default: 50 * 1024 * 1024
25
+
26
+ # Hard ceiling on a single stored artifact, in bytes.
27
+ #
28
+ # Artifacts are buffered in the worker's memory and stored in one database
29
+ # row. Save-time validation and text/CSV append checks reject oversized
30
+ # payloads, but this does not cap task allocations or aggregate process memory.
31
+ # For bulk export, write to object storage and keep only a reference here.
32
+ mattr_accessor :max_artifact_size, default: 64 * 1024 * 1024
33
+
34
+ # Proc to resolve the current user who triggered the run.
35
+ # Must return an object responding to #id (and optionally #email).
36
+ # Example:
37
+ # MaintenanceOnSteroids.current_user_resolver = -> { Current.user }
38
+ mattr_accessor :current_user_resolver, default: nil
39
+
40
+ # Proc to format user display in the UI.
41
+ # Receives the Run record and returns a string.
42
+ # Default shows email if present, otherwise "Type#ID".
43
+ # Examples:
44
+ # MaintenanceOnSteroids.user_display_formatter = ->(run) { run.user_email }
45
+ # MaintenanceOnSteroids.user_display_formatter = ->(run) {
46
+ # User.find(run.user_id).full_name rescue run.user_email
47
+ # }
48
+ mattr_accessor :user_display_formatter, default: nil
49
+
50
+ # --- Authentication / Authorization ---
51
+
52
+ # HTTP Basic Authentication.
53
+ # When enabled, users must provide credentials before any other checks run.
54
+ # Example:
55
+ # MaintenanceOnSteroids.http_basic_authentication_enabled = true
56
+ # MaintenanceOnSteroids.http_basic_authentication_user_name = "admin"
57
+ # MaintenanceOnSteroids.http_basic_authentication_password = Rails.application.credentials.maintenance_password
58
+ # Shipped placeholders. Leaving the password at its default is treated as
59
+ # *no* access control -- a dashboard behind "admin"/"secret" is not
60
+ # protected, and counting it as configured would make the most dangerous
61
+ # setup quieter than an unconfigured one.
62
+ DEFAULT_HTTP_BASIC_USER_NAME = "admin"
63
+ DEFAULT_HTTP_BASIC_PASSWORD = "secret"
64
+
65
+ mattr_accessor :http_basic_authentication_enabled, default: false
66
+ mattr_accessor :http_basic_authentication_user_name, default: DEFAULT_HTTP_BASIC_USER_NAME
67
+ mattr_accessor :http_basic_authentication_password, default: DEFAULT_HTTP_BASIC_PASSWORD
68
+
69
+ # Controller-based access verification.
70
+ # Receives the controller instance. Return true to allow, false to deny.
71
+ # Runs after HTTP Basic (if enabled). Denial renders 403.
72
+ # Example:
73
+ # MaintenanceOnSteroids.verify_access_proc = ->(controller) {
74
+ # controller.current_user&.admin?
75
+ # }
76
+ mattr_accessor :verify_access_proc, default: nil
77
+
78
+ # General-purpose authentication hook (instance_exec'd in controller context).
79
+ # Runs after HTTP Basic and before verify_access_proc. Use for custom auth flows.
80
+ # Example:
81
+ # MaintenanceOnSteroids.authentication = -> {
82
+ # authenticate_user!
83
+ # redirect_to main_app.root_path unless current_user.admin?
84
+ # }
85
+ mattr_accessor :authentication, default: nil
86
+
87
+ # Escape hatch for hosts that gate the dashboard somewhere this gem cannot
88
+ # see (reverse proxy, VPN, Rack middleware). Set it to boot in production
89
+ # without configuring any of the layers above -- an explicit, greppable
90
+ # statement that the exposure is intentional.
91
+ mattr_accessor :allow_insecure_dashboard, default: false
92
+
93
+ # Raised at boot when the dashboard would be reachable with no access
94
+ # control in production.
95
+ class InsecureDashboardError < StandardError; end
96
+
97
+ # Raised by Run#resume! when the job could not be put on the queue. Narrow
98
+ # on purpose: controllers catch this and show the operator why, while any
99
+ # other exception keeps propagating to the app's error reporting.
100
+ class EnqueueFailed < StandardError; end
101
+
102
+ # The run was reaped or claimed by a newer execution. The old worker must stop.
103
+ class ExecutionLost < StandardError; end
104
+
105
+ class << self
106
+ def configure
107
+ yield self
108
+ end
109
+
110
+ # True when HTTP Basic is on *and* carries a password that actually
111
+ # protects anything. Blank counts as unprotected: the controller compares
112
+ # the supplied password against this value, so an empty one authenticates
113
+ # every request -- the common way to get there is a credentials key that
114
+ # is missing or misspelled and quietly resolves to nil.
115
+ def http_basic_authentication_configured?
116
+ http_basic_authentication_enabled && !http_basic_password_unsafe?
117
+ end
118
+
119
+ # The shipped placeholder, or blank -- neither is access control.
120
+ def http_basic_password_unsafe?
121
+ password = http_basic_authentication_password.to_s
122
+
123
+ password.empty? || password == DEFAULT_HTTP_BASIC_PASSWORD
124
+ end
125
+
126
+ # True when at least one access-control layer is meaningfully configured.
127
+ def access_control_configured?
128
+ return true if authentication || verify_access_proc
129
+
130
+ http_basic_authentication_configured?
131
+ end
132
+
133
+ # Called from the engine's after_initialize. Raises in production unless
134
+ # the host opted in via allow_insecure_dashboard; warns everywhere else.
135
+ def verify_access_control!(logger: Rails.logger, env: Rails.env)
136
+ if access_control_configured?
137
+ # An authentication hook alone only verifies *who* the user is --
138
+ # without verify_access_proc every authenticated user gets in.
139
+ if authentication && verify_access_proc.nil?
140
+ logger&.warn(
141
+ "[MaintenanceOnSteroids] `authentication` is configured without `verify_access_proc`: " \
142
+ "any authenticated user can access the maintenance dashboard. Set " \
143
+ "MaintenanceOnSteroids.verify_access_proc to restrict access (unless your " \
144
+ "authentication hook already enforces authorization)."
145
+ )
146
+ end
147
+ return
148
+ end
149
+
150
+ reason =
151
+ if !http_basic_authentication_enabled
152
+ "no access control is configured"
153
+ elsif http_basic_authentication_password.to_s.empty?
154
+ "HTTP Basic is enabled but its password is blank, which authenticates every request"
155
+ else
156
+ "HTTP Basic is enabled but still uses the shipped default password"
157
+ end
158
+
159
+ # Built once and prefixed per branch: deriving the warning by stripping a
160
+ # substring out of the raise message would silently start announcing
161
+ # "Refusing to boot" for boots that were never refused.
162
+ body =
163
+ "#{reason}. Anyone who can reach the mounted dashboard could view every task, read its " \
164
+ "source, and start runs against this database. Set http_basic_authentication_password " \
165
+ "(to something other than blank or the default), authentication, or verify_access_proc " \
166
+ "in config/initializers/maintenance_on_steroids.rb. If the dashboard is already protected " \
167
+ "elsewhere (reverse proxy, VPN, middleware), set " \
168
+ "MaintenanceOnSteroids.allow_insecure_dashboard = true to acknowledge that."
169
+
170
+ if env.production? && !allow_insecure_dashboard
171
+ raise InsecureDashboardError, "[MaintenanceOnSteroids] Refusing to boot: #{body}"
172
+ end
173
+
174
+ logger&.warn("[MaintenanceOnSteroids] #{body}")
175
+ end
176
+
177
+ # Discover all task classes defined in the host app
178
+ def task_classes
179
+ load_all_tasks!
180
+ JobRegistry.tasks
181
+ end
182
+
183
+ def load_all_tasks!
184
+ JobRegistry.load_all!
185
+ end
186
+ end
187
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: maintenance_on_steroids
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Igor Kasyanchuk
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 8.1.3.1
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: 8.1.3.1
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9'
32
+ - !ruby/object:Gem::Dependency
33
+ name: csv
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: 'A powerful maintenance task runner leveraging ActiveJob::Continuable.
47
+ Features include: form inputs with typed parameters, DB-stored artifacts, database
48
+ role switching, live progress UI, pause/resume/cancel, and more.'
49
+ email:
50
+ - igorkasyanchuk@gmail.com
51
+ executables: []
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - CHANGELOG.md
56
+ - LICENSE.txt
57
+ - README.md
58
+ - app/controllers/maintenance_on_steroids/application_controller.rb
59
+ - app/controllers/maintenance_on_steroids/dashboard_controller.rb
60
+ - app/controllers/maintenance_on_steroids/jobs_controller.rb
61
+ - app/controllers/maintenance_on_steroids/runs_controller.rb
62
+ - app/jobs/maintenance_on_steroids/run_job.rb
63
+ - app/models/maintenance_on_steroids/application_record.rb
64
+ - app/models/maintenance_on_steroids/artifact.rb
65
+ - app/models/maintenance_on_steroids/run.rb
66
+ - app/views/layouts/maintenance_on_steroids/application.html.erb
67
+ - app/views/maintenance_on_steroids/dashboard/index.html.erb
68
+ - app/views/maintenance_on_steroids/jobs/index.html.erb
69
+ - app/views/maintenance_on_steroids/jobs/show.html.erb
70
+ - app/views/maintenance_on_steroids/jobs/source.html.erb
71
+ - app/views/maintenance_on_steroids/runs/new.html.erb
72
+ - app/views/maintenance_on_steroids/runs/show.html.erb
73
+ - app/views/maintenance_on_steroids/shared/_auto_refresh.html.erb
74
+ - app/views/maintenance_on_steroids/shared/_javascript.html.erb
75
+ - app/views/maintenance_on_steroids/shared/_pager.html.erb
76
+ - app/views/maintenance_on_steroids/shared/_styles.html.erb
77
+ - app/views/maintenance_on_steroids/shared/_task_list_item.html.erb
78
+ - config/routes.rb
79
+ - lib/generators/maintenance_on_steroids/install/install_generator.rb
80
+ - lib/generators/maintenance_on_steroids/install/templates/create_maintenance_on_steroids_tables.rb.erb
81
+ - lib/generators/maintenance_on_steroids/install/templates/initializer.rb
82
+ - lib/generators/maintenance_on_steroids/job/job_generator.rb
83
+ - lib/generators/maintenance_on_steroids/job/templates/job.rb.erb
84
+ - lib/maintenance_on_steroids.rb
85
+ - lib/maintenance_on_steroids/about_dsl.rb
86
+ - lib/maintenance_on_steroids/artifact_dsl.rb
87
+ - lib/maintenance_on_steroids/artifacts_proxy.rb
88
+ - lib/maintenance_on_steroids/callbacks_dsl.rb
89
+ - lib/maintenance_on_steroids/csv_artifact.rb
90
+ - lib/maintenance_on_steroids/engine.rb
91
+ - lib/maintenance_on_steroids/form_dsl.rb
92
+ - lib/maintenance_on_steroids/instrumentation.rb
93
+ - lib/maintenance_on_steroids/job_dsl.rb
94
+ - lib/maintenance_on_steroids/job_registry.rb
95
+ - lib/maintenance_on_steroids/jsonb_artifact.rb
96
+ - lib/maintenance_on_steroids/params_proxy.rb
97
+ - lib/maintenance_on_steroids/task.rb
98
+ - lib/maintenance_on_steroids/text_artifact.rb
99
+ - lib/maintenance_on_steroids/version.rb
100
+ homepage: https://github.com/igorkasyanchuk/maintenance_on_steroids
101
+ licenses:
102
+ - MIT
103
+ metadata:
104
+ source_code_uri: https://github.com/igorkasyanchuk/maintenance_on_steroids
105
+ changelog_uri: https://github.com/igorkasyanchuk/maintenance_on_steroids/blob/main/CHANGELOG.md
106
+ bug_tracker_uri: https://github.com/igorkasyanchuk/maintenance_on_steroids/issues
107
+ rubygems_mfa_required: 'true'
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: 3.2.0
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubygems_version: 3.7.2
123
+ specification_version: 4
124
+ summary: Maintenance tasks on steroids for Rails 8.1+
125
+ test_files: []