lib/cc/analyzer/engine.rb (89 lines of code) (raw):
require "securerandom"
module CC
module Analyzer
#
# Running specifically an Engine container
#
# Input:
# - name
# - metadata
# - image
# - command (optional)
# - config (becomes /config.json)
# - label
# - io (to write filtered, validated output)
#
# Output:
# - Container::Result
#
class Engine
Error = Class.new(StandardError)
def initialize(name, metadata, config, label)
@name = name
@metadata = metadata
@config = config
@label = label.to_s
@error = nil
end
def run(io)
write_config_file
container = Container.new(
image: metadata.fetch("image"),
command: metadata["command"],
name: container_name,
)
container.on_output("\0") do |output|
handle_output(container, io, output)
end
container.run(container_options).tap do |result|
result.merge_from_exception(error) if error.present?
end
ensure
delete_config_file
end
private
attr_reader :name, :metadata
attr_accessor :error
def handle_output(container, io, raw_output)
output = EngineOutput.new(name, raw_output)
return if output_filter.filter?(output)
unless output.valid?
self.error = Error.new("engine produced invalid output: #{output.error}")
container.stop("output invalid")
end
unless io.write(output_overrider.apply(output).to_json)
self.error = Error.new("#{io.class}#write returned false, indicating an error")
container.stop("output error")
end
end
def qualified_name
"#{name}:#{@config.fetch("channel", "stable")}"
end
def container_options
options = [
"--cap-drop", "all",
"--label", "com.codeclimate.label=#{@label}",
"--log-driver", "none",
"--memory-swap", "-1",
"--net", "none",
"--rm",
"--volume", "#{code.host_path}:/code:ro",
"--volume", "#{config_file.host_path}:/config.json:ro",
"--user", "9000:9000"
]
if (memory = metadata["memory"]).present?
options.concat(["--memory", memory.to_s])
end
options
end
def container_name
@container_name ||= "cc-engines-#{qualified_name.tr(":", "-")}-#{SecureRandom.uuid}"
end
def write_config_file
@config["debug"] = ENV["CODECLIMATE_DEBUG"]
Analyzer.logger.debug "/config.json content: #{@config.inspect}"
config_file.write(@config.to_json)
end
def delete_config_file
config_file.delete if config_file.file?
end
def code
@code ||= MountedPath.code
end
def config_file
@config_file ||= MountedPath.tmp.join(SecureRandom.uuid)
end
def output_filter
@output_filter ||= EngineOutputFilter.new(@config)
end
def output_overrider
@output_overrider ||= EngineOutputOverrider.new(@config)
end
end
end
end