Class: CommandJob
- Inherits:
-
Object
- Object
- 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 ⇒ Object
Steps to execute after a run.
-
#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 ⇒ Object
(also: #perform_now)
Perform the command 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
383 384 385 |
# File 'lib/app/models/command_job.rb', line 383 def add_log() logs.create!(message: ) end |
#after_run ⇒ Object
Steps to execute after a run
198 199 200 201 202 203 204 205 206 207 |
# File 'lib/app/models/command_job.rb', line 198 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 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
367 368 369 370 371 372 373 374 375 376 377 378 |
# File 'lib/app/models/command_job.rb', line 367 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
284 285 286 287 |
# File 'lib/app/models/command_job.rb', line 284 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
270 271 272 273 274 275 276 277 278 279 |
# File 'lib/app/models/command_job.rb', line 270 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
259 260 261 262 263 264 265 |
# File 'lib/app/models/command_job.rb', line 259 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
353 354 355 356 357 358 359 360 361 |
# File 'lib/app/models/command_job.rb', line 353 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
312 313 314 315 316 317 |
# File 'lib/app/models/command_job.rb', line 312 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 ⇒ Object Also known as: perform_now
Perform the command job
213 214 215 216 217 218 219 220 |
# File 'lib/app/models/command_job.rb', line 213 def perform before_run run after_run rescue StandardError => error log_error 'Unable to start job', error set state: STATE_FAIL, error_message: error. 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
302 303 304 305 306 307 |
# File 'lib/app/models/command_job.rb', line 302 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
292 293 294 295 296 297 |
# File 'lib/app/models/command_job.rb', line 292 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
227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/app/models/command_job.rb', line 227 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
244 245 246 |
# File 'lib/app/models/command_job.rb', line 244 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.
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 |
# File 'lib/app/models/command_job.rb', line 331 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
390 391 392 |
# File 'lib/app/models/command_job.rb', line 390 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
324 325 326 |
# File 'lib/app/models/command_job.rb', line 324 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
251 252 253 254 |
# File 'lib/app/models/command_job.rb', line 251 def write_file(path, contents) File.open(path, 'w') { |f| f.write(contents) } add_log "Saving:\n #{contents}\nto: #{path}" end |