Class: BoltServer::TransportApp
- Inherits:
-
Sinatra::Base
- Object
- Sinatra::Base
- BoltServer::TransportApp
- Defined in:
- lib/bolt_server/transport_app.rb
Constant Summary collapse
- PARTIAL_SCHEMAS =
These partial schemas are reused to build multiple request schemas
%w[target-any target-ssh target-winrm task].freeze
- REQUEST_SCHEMAS =
These schemas combine shared schemas to describe client requests
%w[ action-check_node_connections action-run_command action-run_task action-run_script action-upload_file transport-ssh transport-winrm ].freeze
- ACTIONS =
%w[ check_node_connections run_command run_task run_script upload_file ].freeze
Instance Method Summary collapse
- #check_node_connections(targets, body) ⇒ Object
- #in_pe_pal_env(environment) ⇒ Object
-
#initialize(config) ⇒ TransportApp
constructor
A new instance of TransportApp.
- #make_ssh_target(target_hash) ⇒ Object
- #make_winrm_target(target_hash) ⇒ Object
-
#result_set_to_status_hash(result_set, aggregate: false) ⇒ Object
Turns a Bolt::ResultSet object into a status hash that is fit to return to the client in a response.
- #run_command(target, body) ⇒ Object
- #run_script(target, body) ⇒ Object
- #run_task(target, body) ⇒ Object
- #scrub_stack_trace(result) ⇒ Object
- #upload_file(target, body) ⇒ Object
- #validate_schema(schema, body) ⇒ Object
Constructor Details
#initialize(config) ⇒ TransportApp
Returns a new instance of TransportApp.
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/bolt_server/transport_app.rb', line 36 def initialize(config) @config = config @schemas = Hash[REQUEST_SCHEMAS.map do |basename| [basename, JSON.parse(File.read(File.join(__dir__, ['schemas', "#{basename}.json"])))] end] PARTIAL_SCHEMAS.each do |basename| schema_content = JSON.parse(File.read(File.join(__dir__, ['schemas', 'partials', "#{basename}.json"]))) shared_schema = JSON::Schema.new(schema_content, Addressable::URI.parse("partial:#{basename}")) JSON::Validator.add_schema(shared_schema) end @executor = Bolt::Executor.new(0) @file_cache = BoltServer::FileCache.new(@config).setup # This is needed until the PAL is threadsafe. @pal_mutex = Mutex.new super(nil) end |
Instance Method Details
#check_node_connections(targets, body) ⇒ Object
117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/bolt_server/transport_app.rb', line 117 def check_node_connections(targets, body) error = validate_schema(@schemas["action-check_node_connections"], body) return [], error unless error.nil? # Puppet Enterprise's orchestrator service uses the # check_node_connections endpoint to check whether nodes that should be # contacted over SSH or WinRM are responsive. The wait time here is 0 # because the endpoint is meant to be used for a single check of all # nodes; External implementations of wait_until_available (like # orchestrator's) should contact the endpoint in their own loop. [@executor.wait_until_available(targets, wait_time: 0), nil] end |
#in_pe_pal_env(environment) ⇒ Object
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/bolt_server/transport_app.rb', line 183 def in_pe_pal_env(environment) if environment.nil? [400, '`environment` is a required argument'] else @pal_mutex.synchronize do begin pal = BoltServer::PE::PAL.new({}, environment) yield pal rescue Puppet::Environments::EnvironmentNotFound [400, { "class" => 'bolt/unknown-environment', "message" => "Environment #{environment} not found" }.to_json] rescue Bolt::Error => e [400, e.to_json] end end end end |
#make_ssh_target(target_hash) ⇒ Object
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
# File 'lib/bolt_server/transport_app.rb', line 235 def make_ssh_target(target_hash) defaults = { 'host-key-check' => false } overrides = { 'load-config' => false } opts = defaults.merge(target_hash.clone).merge(overrides) if opts['private-key-content'] private_key_content = opts.delete('private-key-content') opts['private-key'] = { 'key-data' => private_key_content } end Bolt::Target.new(target_hash['hostname'], opts) end |
#make_winrm_target(target_hash) ⇒ Object
274 275 276 277 278 279 280 281 |
# File 'lib/bolt_server/transport_app.rb', line 274 def make_winrm_target(target_hash) overrides = { 'protocol' => 'winrm' } opts = target_hash.clone.merge(overrides) Bolt::Target.new(target_hash['hostname'], opts) end |
#result_set_to_status_hash(result_set, aggregate: false) ⇒ Object
Turns a Bolt::ResultSet object into a status hash that is fit to return to the client in a response.
If the ‘result_set` has more than one result, the status hash will have a `status` value and a list of target `results`. If the `result_set` contains only one item, it will be returned as a single result object. Set `aggregate` to treat it as a set of results with length 1 instead.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/bolt_server/transport_app.rb', line 82 def result_set_to_status_hash(result_set, aggregate: false) scrubbed_results = result_set.map do |result| scrub_stack_trace(result.status_hash) end if aggregate || scrubbed_results.length > 1 # For actions that act on multiple targets, construct a status hash for the aggregate result all_succeeded = scrubbed_results.all? { |r| r[:status] == 'success' } { status: all_succeeded ? 'success' : 'failure', result: scrubbed_results } else # If there was only one target, return the first result on its own scrubbed_results.first end end |
#run_command(target, body) ⇒ Object
109 110 111 112 113 114 115 |
# File 'lib/bolt_server/transport_app.rb', line 109 def run_command(target, body) error = validate_schema(@schemas["action-run_command"], body) return [], error unless error.nil? command = body['command'] [@executor.run_command(target, command), nil] end |
#run_script(target, body) ⇒ Object
173 174 175 176 177 178 179 180 181 |
# File 'lib/bolt_server/transport_app.rb', line 173 def run_script(target, body) error = validate_schema(@schemas["action-run_script"], body) return [], error unless error.nil? # Download the file onto the machine. file_location = @file_cache.update_file(body['script']) [@executor.run_script(target, file_location, body['arguments'])] end |
#run_task(target, body) ⇒ Object
100 101 102 103 104 105 106 107 |
# File 'lib/bolt_server/transport_app.rb', line 100 def run_task(target, body) error = validate_schema(@schemas["action-run_task"], body) return [], error unless error.nil? task = Bolt::Task::PuppetServer.new(body['task'], @file_cache) parameters = body['parameters'] || {} [@executor.run_task(target, task, parameters), nil] end |
#scrub_stack_trace(result) ⇒ Object
58 59 60 61 62 63 |
# File 'lib/bolt_server/transport_app.rb', line 58 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 |
#upload_file(target, body) ⇒ Object
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/bolt_server/transport_app.rb', line 130 def upload_file(target, body) error = validate_schema(@schemas["action-upload_file"], body) return [], error unless error.nil? files = body['files'] destination = body['destination'] job_id = body['job_id'] cache_dir = @file_cache.create_cache_dir(job_id.to_s) FileUtils.mkdir_p(cache_dir) files.each do |file| relative_path = file['relative_path'] uri = file['uri'] sha256 = file['sha256'] kind = file['kind'] path = File.join(cache_dir, relative_path) if kind == 'file' # The parent should already be created by `directory` entries, # but this is to be on the safe side. parent = File.dirname(path) FileUtils.mkdir_p(parent) @file_cache.serial_execute { @file_cache.download_file(path, sha256, uri) } elsif kind == 'directory' # Create directory in cache so we can move files in. FileUtils.mkdir_p(path) else return [400, Bolt::Error.new("Invalid `kind` of '#{kind}' supplied. Must be `file` or `directory`.", 'boltserver/schema-error').to_json] end end # We need to special case the scenario where only one file was # included in the request to download. Otherwise, the call to upload_file # will attempt to upload with a directory as a source and potentially a # filename as a destination on the host. In that case the end result will # be the file downloaded to a directory with the same name as the source # filename, rather than directly to the filename set in the destination. upload_source = if files.size == 1 && files[0]['kind'] == 'file' File.join(cache_dir, files[0]['relative_path']) else cache_dir end [@executor.upload_file(target, upload_source, destination), nil] end |
#validate_schema(schema, body) ⇒ Object
65 66 67 68 69 70 71 72 |
# File 'lib/bolt_server/transport_app.rb', line 65 def validate_schema(schema, body) schema_error = JSON::Validator.fully_validate(schema, body) if schema_error.any? Bolt::Error.new("There was an error validating the request body.", 'boltserver/schema-error', schema_error) end end |