Class: Aspera::AsCmd
- Inherits:
-
Object
- Object
- Aspera::AsCmd
- Defined in:
- lib/aspera/ascmd.rb
Overview
Run ascmd
commands using specified executor (usually, remotely on transfer node) Equivalent of SDK “command client” execute: “ascmd -h” to get syntax Note: “ls” can take filters: as_ls -f *.txt -f *.bin /
Defined Under Namespace
Classes: Error
Constant Summary collapse
- OPERATIONS =
list of supported actions
OPS_ARGS.keys.freeze
Class Method Summary collapse
-
.field_description(struct_name, typed_buffer) ⇒ Object
get description of structure’s field, @param struct_name, @param typed_buffer provides field name.
-
.parse(buffer, type_name, indent_level = nil) ⇒ Object
decodes the provided buffer as provided type name :base : value, :buffer_list : an array of btype,buffer, :field_list : a hash, or array.
Instance Method Summary collapse
-
#execute_single(action_sym, arguments) ⇒ Object
execute an “as” command on a remote server.
-
#initialize(command_executor) ⇒ AsCmd
constructor
@param command_executor [Object] provides the “execute” method, taking a command to execute, and stdin to feed to it, typically: ssh or local.
Constructor Details
#initialize(command_executor) ⇒ AsCmd
@param command_executor [Object] provides the “execute” method, taking a command to execute, and stdin to feed to it, typically: ssh or local
77 78 79 |
# File 'lib/aspera/ascmd.rb', line 77 def initialize(command_executor) @command_executor = command_executor end |
Class Method Details
.field_description(struct_name, typed_buffer) ⇒ Object
get description of structure’s field, @param struct_name, @param typed_buffer provides field name
159 160 161 162 163 |
# File 'lib/aspera/ascmd.rb', line 159 def field_description(struct_name, typed_buffer) result = TYPES_DESCR[struct_name][:fields][typed_buffer[:btype] - ENUM_START] raise "Unrecognized field for #{struct_name}: #{typed_buffer[:btype]}\n#{typed_buffer[:buffer]}" if result.nil? return result end |
.parse(buffer, type_name, indent_level = nil) ⇒ Object
decodes the provided buffer as provided type name :base : value, :buffer_list : an array of btype,buffer, :field_list : a hash, or array
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/aspera/ascmd.rb', line 168 def parse(buffer, type_name, indent_level=nil) indent_level = (indent_level || -1) + 1 type_descr = TYPES_DESCR[type_name] raise "Unexpected type #{type_name}" if type_descr.nil? Log.log.trace1{"#{' .' * indent_level}parse:#{type_name}:#{type_descr[:decode]}:#{buffer[0, 16]}...".red} result = nil case type_descr[:decode] when :base num_bytes = type_name.eql?(:zstr) ? buffer.length : type_descr[:size] raise 'ERROR:not enough bytes' if buffer.length < num_bytes byte_array = buffer.shift(num_bytes) byte_array = [byte_array] unless byte_array.is_a?(Array) result = byte_array.pack('C*').unpack1(type_descr[:unpack]) result.force_encoding('UTF-8') if type_name.eql?(:zstr) Log.log.trace1{"#{' .' * indent_level}-> base:#{byte_array} -> #{result}"} result = Time.at(result) if type_name.eql?(:epoch) when :buffer_list # return a list of type_buffer result = [] until buffer.empty? btype = parse(buffer, :int8, indent_level) length = parse(buffer, :int32, indent_level) raise 'ERROR:not enough bytes' if buffer.length < length value = buffer.shift(length) result.push({btype: btype, buffer: value}) Log.log.trace1{"#{' .' * indent_level}:buffer_list[#{result.length - 1}] #{result.last}"} end when :field_list # by default the result is one struct result = {} # get individual binary fields parse(buffer, :blist, indent_level).each do |typed_buffer| # what type of field is it ? field_info = field_description(type_name, typed_buffer) Log.log.trace1{"#{' .' * indent_level}+ field(special=#{field_info[:special]})=#{field_info[:name]}".green} case field_info[:special] when nil # normal case result[field_info[:name]] = parse(typed_buffer[:buffer], field_info[:is_a], indent_level) when :return_true # nothing to parse, just return true result[field_info[:name]] = true when :list_multiple # field appears multiple times, and is an array of values (base type) result[field_info[:name]] ||= [] result[field_info[:name]].push(parse(typed_buffer[:buffer], field_info[:is_a], indent_level)) when :list_tlv_list # field is an array of values in a list of buffers result[field_info[:name]] = parse(typed_buffer[:buffer], :blist, indent_level).map{|r|parse(r[:buffer], field_info[:is_a], indent_level)} when :list_tlv_restart # field is an array of values, but a new value is started on index 1 fl = result[field_info[:name]] = [] parse(typed_buffer[:buffer], :blist, indent_level).map do |tb| fl.push({}) if tb[:btype].eql?(ENUM_START) fi = field_description(field_info[:is_a], tb) fl.last[fi[:name]] = parse(tb[:buffer], fi[:is_a], indent_level) end end end else Aspera.error_unexpected_value(type_descr[:decode]) end return result end |
Instance Method Details
#execute_single(action_sym, arguments) ⇒ Object
execute an “as” command on a remote server
85 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 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/aspera/ascmd.rb', line 85 def execute_single(action_sym, arguments) arguments = [] if arguments.nil? Log.log.debug{"execute_single:#{action_sym}:#{arguments}"} Aspera.assert_type(action_sym, Symbol) Aspera.assert_type(arguments, Array) Aspera.assert(arguments.all?(String), 'arguments must be strings') # lines of commands (String's) command_lines = [] # add "as_" command main_command = "as_#{action_sym}" arg_batches = if OPS_ARGS[action_sym].nil? || OPS_ARGS[action_sym].zero? [arguments] else # split arguments into batches arguments.each_slice(OPS_ARGS[action_sym]).to_a end arg_batches.each do |args| command = [main_command] # enclose arguments in double quotes, protect backslash and double quotes # ascmd uses space as token separator, and optional quotes ('") or \ to escape args.each do |v| command.push(%Q{"#{v.gsub(/["\\]/){|s|"\\#{s}"}}"}) end command_lines.push(command.join(' ')) end command_lines.push('as_exit') command_lines.push('') # execute the main command and then exit stdin_input = command_lines.join("\n") Log.log.trace1{"execute_single:#{stdin_input}"} # execute, get binary output byte_buffer = @command_executor.execute('ascmd', stdin_input).unpack('C*') raise 'ERROR: empty answer from server' if byte_buffer.empty? # get hash or table result result = self.class.parse(byte_buffer, :result) raise 'ERROR: unparsed bytes remaining' unless byte_buffer.empty? # get and delete info,always present in results system_info = result[:info] result.delete(:info) # make single file result like a folder result[:dir] = [result.delete(:file)] if result.key?(:file) # add type field for stats if result.key?(:dir) result[:dir].each do |file| if file.key?(:smode) # Converts the first character of the file mode (see 'man ls') into a type. file[:type] = case file[:smode][0, 1]; when 'd' then:directory; when '-' then:file; when 'l' then:link; else; :other; end # rubocop:disable Style/Semicolon end end end # for info, second overrides first, so restore it case result.keys.length when 0 then result = system_info when 1 then result = result[result.keys.first] else Aspera.error_unexpected_value(result.keys.length) end # raise error as exception raise Error.new(result[:errno], result[:errstr], action_sym, arguments) if result.is_a?(Hash) && (result.keys.sort == TYPES_DESCR[:error][:fields].map{|i|i[:name]}.sort) return result end |