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.
-
#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.
-
#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
Who started this job.
-
#download_file(file_url, file_path) ⇒ Object
Download a file to the given path.
-
#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
Default time to keep a job before auto archiving it.
-
#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
365 366 367 |
# File 'lib/app/models/command_job.rb', line 365 def add_log() logs.create!(message: ) end |
#after_run ⇒ Object
Steps to execute after a run
180 181 182 183 184 185 186 187 188 189 |
# File 'lib/app/models/command_job.rb', line 180 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
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/app/models/command_job.rb', line 150 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 |
#cancelled? ⇒ Boolean
If we are cancelled
108 109 110 |
# File 'lib/app/models/command_job.rb', line 108 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
349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/app/models/command_job.rb', line 349 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 |
#completed? ⇒ Boolean
If we is finished, failed or success
92 93 94 |
# File 'lib/app/models/command_job.rb', line 92 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
266 267 268 269 |
# File 'lib/app/models/command_job.rb', line 266 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
252 253 254 255 256 257 258 259 260 261 |
# File 'lib/app/models/command_job.rb', line 252 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
132 133 134 135 136 |
# File 'lib/app/models/command_job.rb', line 132 def current_status status = { state: state } status[:message] = if .present? status end |
#display_started_by ⇒ Object
Who started this job
43 44 45 |
# File 'lib/app/models/command_job.rb', line 43 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
241 242 243 244 245 246 247 |
# File 'lib/app/models/command_job.rb', line 241 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 |
#failure? ⇒ Boolean
True if in fail status
85 86 87 |
# File 'lib/app/models/command_job.rb', line 85 def failure? job_state?(STATE_FAIL) end |
#failure_or_cancelled? ⇒ Boolean
112 113 114 |
# File 'lib/app/models/command_job.rb', line 112 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.
121 122 123 124 125 126 |
# File 'lib/app/models/command_job.rb', line 121 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
335 336 337 338 339 340 341 342 343 |
# File 'lib/app/models/command_job.rb', line 335 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
294 295 296 297 298 299 |
# File 'lib/app/models/command_job.rb', line 294 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
57 58 59 |
# File 'lib/app/models/command_job.rb', line 57 def name self.class.to_s.underscore.humanize end |
#new_job? ⇒ Boolean
True if in new status
64 65 66 |
# File 'lib/app/models/command_job.rb', line 64 def new_job? job_state?(STATE_NEW) end |
#perform ⇒ Object Also known as: perform_now
Perform the command job
195 196 197 198 199 200 201 202 |
# File 'lib/app/models/command_job.rb', line 195 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
141 142 143 |
# File 'lib/app/models/command_job.rb', line 141 def perform_later perform end |
#remove_dir(dir_path) ⇒ Object
Remove the given file name
284 285 286 287 288 289 |
# File 'lib/app/models/command_job.rb', line 284 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
274 275 276 277 278 279 |
# File 'lib/app/models/command_job.rb', line 274 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
209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/app/models/command_job.rb', line 209 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
226 227 228 |
# File 'lib/app/models/command_job.rb', line 226 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.
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
# File 'lib/app/models/command_job.rb', line 313 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
99 100 101 |
# File 'lib/app/models/command_job.rb', line 99 def running? !completed? end |
#sort_fields ⇒ Object
Which to sort by
372 373 374 |
# File 'lib/app/models/command_job.rb', line 372 def sort_fields %i[created_at] end |
#succeeded? ⇒ Boolean
True if in success status
78 79 80 |
# File 'lib/app/models/command_job.rb', line 78 def succeeded? job_state?(STATE_SUCCESS) end |
#ttl ⇒ Object
Default time to keep a job before auto archiving it
50 51 52 |
# File 'lib/app/models/command_job.rb', line 50 def ttl 30 end |
#unzip_file(file_path, to_dir) ⇒ Object
Unzip a given file
306 307 308 |
# File 'lib/app/models/command_job.rb', line 306 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
71 72 73 |
# File 'lib/app/models/command_job.rb', line 71 def work_in_progress? job_state?([STATE_WIP, STATE_RETRYING]) end |
#write_file(path, contents) ⇒ Object
Write out the contents to the file
233 234 235 236 |
# File 'lib/app/models/command_job.rb', line 233 def write_file(path, contents) File.open(path, 'w') { |f| f.write(contents) } add_log "Saving:\n #{contents}\nto: #{path}" end |