Class: CommandJob
- Includes:
- StandardModel
- Defined in:
- lib/app/models/command_job.rb
Overview
Base class for all jobs that will be run on builds
Constant Summary collapse
- STATE_NEW =
Constants
'new'
- STATE_WIP =
'working'
- STATE_RETRYING =
'retrying'
- STATE_SUCCESS =
'success'
- STATE_FAIL =
'failure'
- STATE_CANCELLED =
'cancelled'
- ALL_STATES =
[STATE_NEW, STATE_WIP, STATE_RETRYING, STATE_CANCELLED, STATE_SUCCESS, STATE_FAIL].freeze
Instance Method Summary collapse
-
#add_log(message) ⇒ Object
Add a job log message.
-
#after_run ⇒ String
abstract
-
state of the job.
-
-
#before_run ⇒ Object
Steps to execute before a run.
- #cancel!(actor) ⇒ Object abstract
-
#cancelled? ⇒ Boolean
If we are cancelled.
-
#check_for_text(output, texts = [], inclusive_check: true, output_limit: -1)) ⇒ Object
Check if any occurrences were found (or not found) For most command jobs, we want to see the full output.
- #child_jobs ⇒ Object
-
#completed? ⇒ Boolean
If we is finished, failed or success.
-
#copy_dir(dir, to_path) ⇒ Object
Copy a given directory to a new location and record the log.
-
#copy_file(from_path, to_path) ⇒ Object
Copy a given file to a new location and record the log.
-
#current_status ⇒ Object
Return the job’s status and information in a hash that could be used to return to a calling api.
-
#display_started_by ⇒ Object
abstract
String - Who started this job if present, otherwise ‘System`.
-
#download_file(file_url, file_path) ⇒ Object
Download a file to the given path.
-
#duration ⇒ Object
abstract
Integer - How long this job took in milliseconds.
-
#failure? ⇒ Boolean
True if in fail status.
- #failure_or_cancelled? ⇒ Boolean
-
#job_state?(states, default_state: false) ⇒ Boolean
Fetch the latest version of this instance from the database and check the state against the required state.
-
#mask_keywords(output, keywords = []) ⇒ Object
Mask keywords if given in the command.
-
#mkdir(dir) ⇒ Object
(also: #make_dir)
Create a directory and record it.
-
#name ⇒ Object
Return the name of this job.
-
#new_job? ⇒ Boolean
True if in new status.
-
#perform ⇒ String
(also: #perform_now)
abstract
-
state of the job.
-
-
#perform_later ⇒ Object
Perform this job in the background.
-
#remove_dir(dir_path) ⇒ Object
Remove the given file name.
-
#remove_file(file_path) ⇒ Object
Remove the given file name.
-
#run ⇒ Object
Run the job, handling any failures that might happen.
-
#run! ⇒ Object
Determine the correct action to take and get it started.
-
#run_command(command, dir = '/tmp', options = {}) ⇒ Object
Run the command capturing the command output and any standard error to the log.
-
#running? ⇒ Boolean
(also: #incomplete?)
Job has not finished, failure or success.
-
#sort_fields ⇒ Object
Which to sort by.
-
#succeeded? ⇒ Boolean
True if in success status.
-
#ttl ⇒ Object
abstract
Integer - TTL for this job.
-
#unzip_file(file_path, to_dir) ⇒ Object
Unzip a given file.
-
#work_in_progress? ⇒ Boolean
True if in WIP status.
-
#write_file(path, contents) ⇒ Object
Write out the contents to the file.
Methods included from StandardModel
#audit_action, #auto_strip_attributes, #capture_user_info, #clear_cache, #created_by_display_name, #delete_and_log, #destroy_and_log, included, #last_modified_by_display_name, #log_change, #log_deletion, #remove_blank_secure_fields, #save_and_log, #save_and_log!, #secure_fields, #update, #update!, #update_and_log, #update_and_log!
Methods included from App47Logger
clean_params, #clean_params, delete_parameter_keys, #log_controller_error, log_debug, #log_debug, log_error, #log_error, log_exception, #log_message, log_message, #log_warn, log_warn, mask_parameter_keys, #update_flash_messages
Instance Method Details
#add_log(message) ⇒ Object
Add a job log message
382 383 384 |
# File 'lib/app/models/command_job.rb', line 382 def add_log() logs.create!(message: ) end |
#after_run ⇒ String
Steps to execute after a run
Returns - state of the job.
197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/app/models/command_job.rb', line 197 def after_run case state when STATE_RETRYING, STATE_WIP set finished_at: Time.now.utc, error_message: nil, state: STATE_SUCCESS when STATE_SUCCESS set finished_at: Time.now.utc, error_message: nil else set finished_at: Time.now.utc end state end |
#before_run ⇒ Object
Steps to execute before a run
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/app/models/command_job.rb', line 168 def before_run case state when STATE_NEW set retries: 0, started_at: Time.now.utc, finished_at: nil, error_message: nil, result: nil, state: STATE_WIP when STATE_RETRYING set retries: 0, started_at: Time.now.utc, finished_at: nil, error_message: nil, result: nil when STATE_FAIL set retries: 0, started_at: Time.now.utc, finished_at: nil, error_message: nil, state: STATE_RETRYING, result: nil else set retries: 0, started_at: Time.now.utc, finished_at: nil, result: nil end end |
#cancel!(actor) ⇒ Object
cancel the current job
43 44 45 46 |
# File 'lib/app/models/command_job.rb', line 43 def cancel!(actor) update!(cancelled_by: actor, cancelled_at: Time.now.utc, state: STATE_CANCELLED) unless completed? child_jobs.each { |j| j.cancel!(actor) } end |
#cancelled? ⇒ Boolean
If we are cancelled
126 127 128 |
# File 'lib/app/models/command_job.rb', line 126 def cancelled? job_state?(STATE_CANCELLED) end |
#check_for_text(output, texts = [], inclusive_check: true, output_limit: -1)) ⇒ Object
Check if any occurrences were found (or not found) For most command jobs, we want to see the full output. -1 accomplishes this
366 367 368 369 370 371 372 373 374 375 376 377 |
# File 'lib/app/models/command_job.rb', line 366 def check_for_text(output, texts = [], inclusive_check: true, output_limit: -1) return if texts.blank? texts = [texts] if texts.is_a?(String) texts.each do |text| if inclusive_check raise "Error: found text (#{text}) - #{output[0...output_limit]}" if output.match?(/#{text}/) else raise "Error: missing text (#{text}) - #{output[0...output_limit]}" unless output.match?(/#{text}/) end end end |
#child_jobs ⇒ Object
48 49 50 |
# File 'lib/app/models/command_job.rb', line 48 def child_jobs [] end |
#completed? ⇒ Boolean
If we is finished, failed or success
110 111 112 |
# File 'lib/app/models/command_job.rb', line 110 def completed? job_state?([STATE_CANCELLED, STATE_FAIL, STATE_SUCCESS]) end |
#copy_dir(dir, to_path) ⇒ Object
Copy a given directory to a new location and record the log
283 284 285 286 |
# File 'lib/app/models/command_job.rb', line 283 def copy_dir(dir, to_path) FileUtils.cp_r dir, to_path add_log "Copy directory from: #{dir} to: #{to_path}" end |
#copy_file(from_path, to_path) ⇒ Object
Copy a given file to a new location and record the log
269 270 271 272 273 274 275 276 277 278 |
# File 'lib/app/models/command_job.rb', line 269 def copy_file(from_path, to_path) if File.exist? from_path FileUtils.cp(from_path, to_path) add_log "Copy file from: #{from_path} to: #{to_path}" else add_log "File not found: #{from_path}, copy not performed" end rescue StandardError => error raise "Unable to copy file from #{from_path} to #{to_path}, error: ##{error.}" end |
#current_status ⇒ Object
Return the job’s status and information in a hash that could be used to return to a calling api
150 151 152 153 154 |
# File 'lib/app/models/command_job.rb', line 150 def current_status status = { state: state } status[:message] = if .present? status end |
#display_started_by ⇒ Object
Who started this job
Returns String - Who started this job if present, otherwise ‘System`.
62 63 64 |
# File 'lib/app/models/command_job.rb', line 62 def display_started_by started_by.present? ? started_by.name : 'System' end |
#download_file(file_url, file_path) ⇒ Object
Download a file to the given path
258 259 260 261 262 263 264 |
# File 'lib/app/models/command_job.rb', line 258 def download_file(file_url, file_path) download = URI.parse(file_url).open IO.copy_stream(download, file_path) add_log "Downloaded file: #{file_url} to #{file_path}" rescue StandardError => error raise "Unable to download file from #{file_url} to #{file_path}, error: ##{error.}" end |
#duration ⇒ Object
duration for the job
Returns Integer - How long this job took in milliseconds.
54 55 56 57 58 |
# File 'lib/app/models/command_job.rb', line 54 def duration succeeded? ? finished_at - started_at : 0 rescue StandardError 0 end |
#failure? ⇒ Boolean
True if in fail status
103 104 105 |
# File 'lib/app/models/command_job.rb', line 103 def failure? job_state?(STATE_FAIL) end |
#failure_or_cancelled? ⇒ Boolean
130 131 132 |
# File 'lib/app/models/command_job.rb', line 130 def failure_or_cancelled? job_state?([STATE_FAIL, STATE_CANCELLED], default_state: true) end |
#job_state?(states, default_state: false) ⇒ Boolean
Fetch the latest version of this instance from the database and check the state against the required state. If there is a match, then return true, otherwise return false. If there is an error, return the default.
139 140 141 142 143 144 |
# File 'lib/app/models/command_job.rb', line 139 def job_state?(states, default_state: false) states.is_a?(Array) ? states.include?(state) : states.eql?(state) rescue StandardError => error App47Logger.log_warn "Unable to check job failed or cancelled #{inspect}", error default_state end |
#mask_keywords(output, keywords = []) ⇒ Object
Mask keywords if given in the command
352 353 354 355 356 357 358 359 360 |
# File 'lib/app/models/command_job.rb', line 352 def mask_keywords(output, keywords = []) return output if keywords.blank? keywords = [keywords] if keywords.is_a?(String) keywords.each do |keyword| output = output.gsub(keyword, '***********') end output end |
#mkdir(dir) ⇒ Object Also known as: make_dir
Create a directory and record it
311 312 313 314 315 316 |
# File 'lib/app/models/command_job.rb', line 311 def mkdir(dir) return if File.exist?(dir) FileUtils.mkdir dir add_log "Created directory: #{dir}" end |
#name ⇒ Object
Return the name of this job
75 76 77 |
# File 'lib/app/models/command_job.rb', line 75 def name self.class.to_s.underscore.humanize end |
#new_job? ⇒ Boolean
True if in new status
82 83 84 |
# File 'lib/app/models/command_job.rb', line 82 def new_job? job_state?(STATE_NEW) end |
#perform ⇒ String Also known as: perform_now
Perform the command job
Returns - state of the job.
211 212 213 214 215 216 217 218 219 |
# File 'lib/app/models/command_job.rb', line 211 def perform before_run run after_run rescue StandardError => error log_error 'Unable to start job', error set state: STATE_FAIL, error_message: error. STATE_FAIL end |
#perform_later ⇒ Object
Perform this job in the background
159 160 161 |
# File 'lib/app/models/command_job.rb', line 159 def perform_later perform end |
#remove_dir(dir_path) ⇒ Object
Remove the given file name
301 302 303 304 305 306 |
# File 'lib/app/models/command_job.rb', line 301 def remove_dir(dir_path) return unless File.exist?(dir_path) FileUtils.remove_dir dir_path add_log "Removing dir: #{dir_path}" end |
#remove_file(file_path) ⇒ Object
Remove the given file name
291 292 293 294 295 296 |
# File 'lib/app/models/command_job.rb', line 291 def remove_file(file_path) return unless File.exist?(file_path) FileUtils.remove_file file_path add_log "Removing file: #{file_path}" end |
#run ⇒ Object
Run the job, handling any failures that might happen
226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/app/models/command_job.rb', line 226 def run run! unless cancelled? rescue StandardError => error if (retries + 1) >= max_retries log_error "Unable to run job id: #{id}, done retrying", error set state: STATE_FAIL, error_message: "Failed final attempt: #{error.}" else log_error "Unable to run job id: #{id}, retrying!!", error add_log "Unable to run job: #{error.}, retrying!!" set error_message: "Failed attempt # #{retries}: #{error.}", retries: retries + 1, state: STATE_RETRYING run end end |
#run! ⇒ Object
Determine the correct action to take and get it started
243 244 245 |
# File 'lib/app/models/command_job.rb', line 243 def run! raise 'Incomplete class, concrete implementation should implement #run!' end |
#run_command(command, dir = '/tmp', options = {}) ⇒ Object
Run the command capturing the command output and any standard error to the log.
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 |
# File 'lib/app/models/command_job.rb', line 330 def run_command(command, dir = '/tmp', = {}) command = command.join(' ') if command.is_a?(Array) output = Tempfile.open('run-command-', '/tmp') do |f| Dir.chdir(dir) { `#{command} > #{f.path} 2>&1` } mask_keywords(f.open.read, [:mask_texts]) end output = 'Success' if output.blank? command = mask_keywords(command, [:mask_texts]) if block_given? yield output else logs.create!(dir: dir, command: command, message: output) end [:output_limit] ||= -1 check_for_text(output, [:error_texts], output_limit: [:output_limit]) check_for_text(output, [:required_texts], inclusive_check: false, output_limit: [:output_limit]) output end |
#running? ⇒ Boolean Also known as: incomplete?
Job has not finished, failure or success
117 118 119 |
# File 'lib/app/models/command_job.rb', line 117 def running? !completed? end |
#sort_fields ⇒ Object
Which to sort by
389 390 391 |
# File 'lib/app/models/command_job.rb', line 389 def sort_fields %i[created_at] end |
#succeeded? ⇒ Boolean
True if in success status
96 97 98 |
# File 'lib/app/models/command_job.rb', line 96 def succeeded? job_state?(STATE_SUCCESS) end |
#ttl ⇒ Object
Default time to keep a job before auto archiving it
Returns Integer - TTL for this job.
68 69 70 |
# File 'lib/app/models/command_job.rb', line 68 def ttl 30 end |
#unzip_file(file_path, to_dir) ⇒ Object
Unzip a given file
323 324 325 |
# File 'lib/app/models/command_job.rb', line 323 def unzip_file(file_path, to_dir) run_command "unzip #{file_path}", to_dir, error_texts: 'unzip:' end |
#work_in_progress? ⇒ Boolean
True if in WIP status
89 90 91 |
# File 'lib/app/models/command_job.rb', line 89 def work_in_progress? job_state?([STATE_WIP, STATE_RETRYING]) end |
#write_file(path, contents) ⇒ Object
Write out the contents to the file
250 251 252 253 |
# File 'lib/app/models/command_job.rb', line 250 def write_file(path, contents) File.open(path, 'w') { |f| f.write(contents) } add_log "Saving:\n #{contents}\nto: #{path}" end |