class Seira::Jobs

Constants

RESOURCE_SIZES
SUMMARY
VALID_ACTIONS

Attributes

action[R]
app[R]
args[R]
context[R]
job_name[R]

Public Class Methods

new(app:, action:, args:, context:) click to toggle source
# File lib/seira/jobs.rb, line 32
def initialize(app:, action:, args:, context:)
  @app = app
  @action = action
  @context = context
  @args = args
  @job_name = args[0]
end

Public Instance Methods

run() click to toggle source
# File lib/seira/jobs.rb, line 40
def run
  case action
  when 'help'
    run_help
  when 'list'
    run_list
  when 'delete'
    run_delete
  when 'run'
    run_run
  else
    fail "Unknown command encountered"
  end
end

Private Instance Methods

discover_job_template_file_name(source) click to toggle source
# File lib/seira/jobs.rb, line 177
def discover_job_template_file_name(source)
  if File.exist?("#{source}/jobs/template.yaml.erb")
    "template.yaml.erb"
  else
    "template.yaml"
  end
end
run_delete() click to toggle source
# File lib/seira/jobs.rb, line 67
def run_delete
  kubectl("delete job #{job_name}", context: context)
end
run_help() click to toggle source
# File lib/seira/jobs.rb, line 57
def run_help
  puts SUMMARY
  puts "\n\n"
  puts "TODO"
end
run_list() click to toggle source
# File lib/seira/jobs.rb, line 63
def run_list
  kubectl("get jobs -o wide", context: context)
end
run_run() click to toggle source
# File lib/seira/jobs.rb, line 71
def run_run
  gcp_app = App.new(app: app, action: 'apply', args: [""], context: context)

  # Set defaults
  async = false # Wait for job to finish before continuing.
  no_delete = false # Delete at end
  resource_hash = RESOURCE_SIZES['1']

  # Loop through args and process any that aren't just the command to run
  loop do
    arg = args.first
    if arg.nil?
      puts 'Please specify a command to run'
      exit(1)
    end

    break unless arg.start_with? '--'

    if arg == '--async'
      async = true
      no_delete = true
    elsif arg == '--no-delete'
      no_delete = true
    elsif arg.start_with?('--size=')
      size = arg.split('=')[1]
      resource_hash = RESOURCE_SIZES[size]
    else
      puts "Warning: Unrecognized argument #{arg}"
    end

    args.shift
  end

  existing_job_names = kubectl("get jobs --output=jsonpath={.items..metadata.name}", context: context, clean_output: true, return_output: true).split(" ").map(&:strip).map { |name| name.gsub(/^#{app}-run-/, '') }
  command = args.join(' ')
  unique_name = "#{app}-run-#{Random.unique_name(existing_job_names)}"
  revision = ENV['REVISION'] || gcp_app.ask_cluster_for_current_revision
  replacement_hash = {
    'UNIQUE_NAME' => unique_name,
    'REVISION' => revision,
    'COMMAND' => %("sh", "-c", "#{command}")
  }.merge(resource_hash)

  source = "kubernetes/#{context[:cluster]}/#{app}" # TODO: Move to method in app.rb
  Dir.mktmpdir do |destination|
    file_name = discover_job_template_file_name(source)

    FileUtils.mkdir_p destination # Create the nested directory
    FileUtils.copy_file "#{source}/jobs/#{file_name}", "#{destination}/#{file_name}"

    # TOOD: Move this into a method since it is copied from app.rb
    text = File.read("#{destination}/#{file_name}")

    # First run it through ERB if it should be
    if file_name.end_with?('.erb')
      locals = {}.merge(replacement_hash)
      renderer = Seira::Util::ResourceRenderer.new(template: text, context: context, locals: locals)
      text = renderer.render
    end

    new_contents = text
    replacement_hash.each do |key, value|
      new_contents.gsub!(key, value)
    end

    target_name = file_name.gsub('.erb', '')

    File.open("#{destination}/#{target_name}", 'w') { |file| file.write(new_contents) }

    kubectl("apply -f #{destination}", context: context)
    log_link = Helpers.log_link(context: context, query: unique_name)
    puts "View logs at: #{log_link}" unless log_link.nil?
  end

  unless async
    # Check job status until it's finished
    print 'Waiting for job to complete...'
    job_spec = nil
    loop do
      job_spec = JSON.parse(kubectl("get job #{unique_name} -o json", context: context, return_output: true, clean_output: true))
      break if !job_spec['status']['succeeded'].nil? || !job_spec['status']['failed'].nil?
      print '.'
      sleep 3
    end

    status =
      if !job_spec['status']['succeeded'].nil?
        "succeeded"
      elsif !job_spec['status']['failed'].nil?
        "failed"
      else
        "unknown"
      end

    if no_delete
      puts "Job finished with status #{status}. Leaving Job object in cluster, clean up manually when confirmed."
    else
      print "Job finished with status #{status}. Deleting Job from cluster for cleanup."
      kubectl("delete job #{unique_name}", context: context)
    end

    # If the job did not succeed, exit nonzero so calling scripts know something went wrong
    exit(1) unless status == "succeeded"
  end
end