Class: Jets::PolyFun::BaseExecutor

Inherits:
Object
  • Object
show all
Extended by:
Memoist
Defined in:
lib/jets/poly_fun/base_executor.rb

Direct Known Subclasses

NodeExecutor, PythonExecutor

Instance Method Summary collapse

Constructor Details

#initialize(task) ⇒ BaseExecutor

Returns a new instance of BaseExecutor.



8
9
10
# File 'lib/jets/poly_fun/base_executor.rb', line 8

def initialize(task)
  @task = task
end

Instance Method Details

#cleanupObject



112
113
114
# File 'lib/jets/poly_fun/base_executor.rb', line 112

def cleanup
  FileUtils.rm_rf(@temp_dir) unless ENV['KEEP_LAMBDA_WRAPPER']
end

#copy_src_to_tempObject



39
40
41
42
43
44
45
46
47
48
49
# File 'lib/jets/poly_fun/base_executor.rb', line 39

def copy_src_to_temp
  app_class = @task.class_name.constantize
  internal = app_class.respond_to?(:internal) && app_class.internal
  src = internal ?
    "#{File.expand_path("../internal", File.dirname(__FILE__))}/#{@task.poly_src_path}" :
    "#{Jets.root}/#{@task.poly_src_path}"
  dest = "#{@temp_dir}/#{@task.poly_src_path}"

  FileUtils.mkdir_p(File.dirname(dest))
  FileUtils.cp(src, dest)
end

#create_tmpdirObject

Mimic Dir.mktmpdir randomness, not using Dir.mktmpdir because that generates the folder at the /tmp level only.



32
33
34
35
36
37
# File 'lib/jets/poly_fun/base_executor.rb', line 32

def create_tmpdir
  random = "#{Time.now.strftime("%Y%d%H")}-#{Process.pid}-#{SecureRandom.hex[0..6]}"
  tmpdir = "#{Jets.build_root}/executor/#{random}"
  FileUtils.mkdir_p(tmpdir)
  tmpdir
end

#handlerObject



116
117
118
119
120
121
122
# File 'lib/jets/poly_fun/base_executor.rb', line 116

def handler
  # Must use the generated CloudFormation template to get the handler because
  # the handler is derived from mutiple sources.
  resource = Jets::Resource::Lambda::Function.new(@task)
  full_handler = resource.properties["Handler"] # full handler here
  File.extname(full_handler).sub(/^./,'') # the extension of the full handler is the handler
end

#lambda_executor_scriptObject



51
52
53
# File 'lib/jets/poly_fun/base_executor.rb', line 51

def lambda_executor_script
  File.dirname("#{@temp_dir}/#{@task.poly_src_path}") + "/lambda_executor" + @task.lang_ext
end

#run(event, context) ⇒ Object

Handler is in properties:

  1. copy lambda function into tmp folder

  2. generate Lang wrapper script

  3. call wrapper script from ruby. Handle stdout and stderr and result. Pass info back to ruby



16
17
18
19
20
21
22
23
# File 'lib/jets/poly_fun/base_executor.rb', line 16

def run(event, context)
  @temp_dir = create_tmpdir
  copy_src_to_temp
  write(code)
  result = run_lambda_executor(event, context)
  cleanup
  result
end

#run_lambda_executor(event, context) ⇒ Object

When polymorphic method errors, this method reproduces an error in the lambda format Here’s some examples to help example:

Example of what the raw python prints out to stderr:

Traceback (most recent call last):
  File "/tmp/jets/lite/executor/20180804-10727-mcs6qk/lambda_executor.py", line 6, in <module>
    resp = handle(event, context)
  File "/tmp/jets/lite/executor/20180804-10727-mcs6qk/index.py", line 22, in handle
    return response({'message': e.message}, 400)
  File "/tmp/jets/lite/executor/20180804-10727-mcs6qk/index.py", line 5, in response
    badcode
NameError: global name 'badcode' is not defined

So last line has the error summary info. Other lines have stack trace after the Traceback indicator.

Example of the reproduced lambda error format:

{
  "errorMessage": "'NameError' object has no attribute 'message'",
  "errorType": "AttributeError",
  "stackTrace": [
    [
      "/var/task/handlers/controllers/posts_controller/python/index.py",
      22,
      "handle",
      "return response({'message': e.message}, 400)"
    ]
  ]
}


86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/jets/poly_fun/base_executor.rb', line 86

def run_lambda_executor(event, context)
  interpreter = @task.lang
  command = %Q|#{interpreter} #{lambda_executor_script} '#{JSON.dump(event)}' '#{JSON.dump(context)}'|
  stdout, stderr, status = Open3.capture3(command)
  # puts "=> #{command}".color(:green)
  # puts "stdout #{stdout}"
  # puts "stderr #{stderr}"
  # puts "status #{status}"
  if status.success?
    stdout
  else
    # We'll mimic the way lambda displays an error.
    # $stderr.puts(stderr) # uncomment to debug
    error_lines = stderr.split("\n")
    error_message = error_lines.pop
    error_type = error_message.split(':').first
    error_lines.shift # remove first line that has the Traceback
    stack_trace = error_lines.reverse # python shows stack trace in opposite order from ruby
    JSON.dump(
      "errorMessage" => error_message,
      "errorType" => error_type, # hardcode
      "stackTrace" => stack_trace
    )
  end
end

#write(code) ⇒ Object



25
26
27
28
# File 'lib/jets/poly_fun/base_executor.rb', line 25

def write(code)
  puts "lambda_executor_script #{lambda_executor_script}" if ENV['KEEP_LAMBDA_WRAPPER']
  IO.write(lambda_executor_script, code)
end