@@ -30,6 +30,13 @@ module BoltServer
       super(nil)
     end
 
+    def scrub_stack_trace(result)
+      if result.dig(:result, '_error', 'details', 'stack_trace')
+        result[:result]['_error']['details'].reject! { |k| k == 'stack_trace' }
+      end
+      result
+    end
+
     get '/' do
       200
     end
# frozen_string_literal: true

require 'sinatra'
require 'bolt'
require 'bolt/target'
require 'bolt/task/puppet_server'
require 'bolt_server/file_cache'
require 'json'
require 'json-schema'

module BoltServer
  class TransportApp < Sinatra::Base
    # This disables Sinatra's error page generation
    set :show_exceptions, false

    def initialize(config)
      @config = config
      @schemas = {
        "ssh-run_task" => JSON.parse(File.read(File.join(__dir__, 'schemas', 'ssh-run_task.json'))),
        "winrm-run_task" => JSON.parse(File.read(File.join(__dir__, 'schemas', 'winrm-run_task.json')))
      }
      shared_schema = JSON::Schema.new(JSON.parse(File.read(File.join(__dir__, 'schemas', 'task.json'))),
                                       Addressable::URI.parse("file:task"))
      JSON::Validator.add_schema(shared_schema)

      @executor = Bolt::Executor.new(0, load_config: false)

      @file_cache = BoltServer::FileCache.new(@config).setup

      super(nil)
    end

    get '/' do
      200
    end

    if ENV['RACK_ENV'] == 'dev'
      get '/admin/gc' do
        GC.start
        200
      end
    end

    get '/admin/gc_stat' do
      [200, GC.stat.to_json]
    end

    get '/500_error' do
      raise 'Unexpected error'
    end

    post '/ssh/run_task' do
      content_type :json

      body = JSON.parse(request.body.read)
      schema_error = JSON::Validator.fully_validate(@schemas["ssh-run_task"], body)
      return [400, schema_error.join] if schema_error.any?

      opts = body['target']
      if opts['private-key-content']
        opts['private-key'] = { 'key-data' => opts['private-key-content'] }
        opts.delete('private-key-content')
      end

      target = [Bolt::Target.new(body['target']['hostname'], opts)]

      task = Bolt::Task::PuppetServer.new(body['task'], @file_cache)

      parameters = body['parameters'] || {}

      # Since this will only be on one node we can just return the first result
      results = @executor.run_task(target, task, parameters)
      [200, results.first.to_json]
    end

    post '/winrm/run_task' do
      content_type :json

      body = JSON.parse(request.body.read)
      schema_error = JSON::Validator.fully_validate(@schemas["winrm-run_task"], body)
      return [400, schema_error.join] if schema_error.any?

      opts = body['target'].merge('protocol' => 'winrm')

      target = [Bolt::Target.new(body['target']['hostname'], opts)]

      task = Bolt::Task::PuppetServer.new(body['task'], @file_cache)

      parameters = body['parameters'] || {}

      # Since this will only be on one node we can just return the first result
      results = @executor.run_task(target, task, parameters)
      [200, results.first.to_json]
    end

    error 404 do
      [404, "Could not find route #{request.path}"]
    end

    error 500 do
      e = env['sinatra.error']
      [500, "500: Unknown error: #{e.message}"]
    end
  end
end

This name doesn't imply anything about jsonifying the result, I'd rather leave that separate.