Module: Rutty::Actions
- Included in:
- Runner
- Defined in:
- lib/rutty/actions.rb
Overview
The primary mixin module containing the code executed by the rutty bin’s actions.
Instance Method Summary collapse
-
#add_node(args, options) ⇒ void
Add a new user-defined node to the datastore file.
-
#connect_to_nodes(nodes, debug = false) ⇒ Array<Net:SSH::Connection>
private
Sets up the Net::SSH connections given a filled Nodes object.
-
#dsh(args, options) ⇒ void
Cycle through all the user-defined nodes, filtered by the options, connect to them and run the specified command on them.
-
#generate_output ⇒ String
private
Generates the output of the remote actions, based on the value of Runner#output_format.
-
#init(dir) ⇒ void
Initialize the Rutty config file structure in the specified directory, or report that the files already exist there.
-
#list_nodes(args, options) ⇒ void
List all the user-defined nodes currently stored in the datastore file.
-
#scp(args, options) ⇒ void
Cycle through all the user-defined nodes, filtered by the options, connect to them and upload the specified file(s).
Instance Method Details
#add_node(args, options) ⇒ void
Add a new user-defined node to the datastore file.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/rutty/actions.rb', line 66 def add_node args, raise Rutty::BadUsage.new "Must supply a hostname or IP address. See `rutty help add_node' for usage" if args.empty? hash = { :host => args.first } hash[:keypath] = .keypath unless .keypath.nil? hash[:user] = .user unless .user.nil? hash[:port] = .port unless .port.nil? hash[:tags] = . unless ..nil? self.nodes << Rutty::Node.new(hash, self.config.to_hash) self.nodes.write_config self.config_dir say "<%= color('Added #{hash[:host]}', :green) %>" end |
#connect_to_nodes(nodes, debug = false) ⇒ Array<Net:SSH::Connection> (private)
Sets up the Net::SSH connections given a filled Nodes object.
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/rutty/actions.rb', line 251 def connect_to_nodes nodes, debug = false require 'logger' require 'net/ssh' require 'net/scp' require 'fastthread' require 'work_queue' connections = [] cons_lock = Mutex.new returns_lock = Mutex.new pool = WorkQueue.new 10 nodes.each do |node| pool.enqueue_b do returns_lock.synchronize { @returns[node.host] = { :exit => 0, :out => '' } } begin conn = Net::SSH.start(node.host, node.user, :port => node.port, :paranoid => false, :user_known_hosts_file => '/dev/null', :keys => [node.keypath], :timeout => 5, :logger => Logger.new(debug ? $stderr : $stdout), :verbose => (debug ? Logger::FATAL : Logger::DEBUG)) cons_lock.synchronize { connections << conn } rescue Errno::ECONNREFUSED returns_lock.synchronize { @returns[node.host][:out] = "ERROR: Connection refused" @returns[node.host][:exit] = 10000 } cons_lock.synchronize { connections << nil } rescue SocketError returns_lock.synchronize { @returns[node.host][:out] = "ERROR: no nodename nor servname provided, or not known" @returns[node.host][:exit] = 20000 } cons_lock.synchronize { connections << nil } rescue Timeout::Error returns_lock.synchronize { @returns[node.host][:out] = "ERROR: Connection timeout" @returns[node.host][:exit] = 30000 } cons_lock.synchronize { connections << nil } rescue Net::SSH::AuthenticationFailed => e returns_lock.synchronize { @returns[node.host][:out] = "ERROR: Authentication failure (#{e.to_s})" @returns[node.host][:exit] = 40000 } cons_lock.synchronize { connections << nil } end end end loop do break if nodes.length == connections.length sleep 0.2 end pool.stop connections.compact end |
#dsh(args, options) ⇒ void
Cycle through all the user-defined nodes, filtered by the options, connect to them and run the specified command on them.
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 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 |
# File 'lib/rutty/actions.rb', line 141 def dsh args, @start_time = Time.now check_installed! raise Rutty::BadUsage.new "Must supply a command to run. See `rutty help dsh' for usage" if args.empty? raise Rutty::BadUsage.new "One of -a or --tags must be passed" if .a.nil? and ..nil? raise Rutty::BadUsage.new "Use either -a or --tags, not both" if !.a.nil? and !..nil? raise Rutty::BadUsage.new "Multi-word commands must be enclosed in quotes (ex. rutty -a \"ps -ef | grep httpd\")" if args.length > 1 if self.nodes.empty? say "<%= color('No nodes defined', :yellow) %>" exit end com_str = args.pop @returns = {} # This is necessary in order to capture exit codes and/or signals, # which are't passed through when using just the ssh.exec!() semantics. exec_command = lambda { |ssh| ssh.open_channel do |channel| channel.exec(com_str) do |ch, success| unless success @returns[ssh.host][:out] = "FAILED: couldn't execute command (ssh.channel.exec failure)" end channel.on_data do |ch, data| # stdout @returns[ssh.host][:out] << data end channel.on_extended_data do |ch, type, data| next unless type == 1 # only handle stderr @returns[ssh.host][:out] << data end channel.on_request("exit-status") do |ch, data| @returns[ssh.host][:exit] = data.read_long end channel.on_request("exit-signal") do |ch, data| @returns[ssh.host][:sig] = data.read_long end end end ssh.loop } @filtered_nodes = self.nodes.filter() connections = connect_to_nodes @filtered_nodes, .debug.nil? connections.each { |ssh| exec_command.call(ssh) } loop do connections.delete_if { |ssh| !ssh.process(0.1) { |s| s.busy? } } break if connections.empty? end say generate_output end |
#generate_output ⇒ String (private)
Generates the output of the remote actions, based on the value of Runner#output_format.
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
# File 'lib/rutty/actions.rb', line 326 def generate_output case self.output_format when 'human-readable' HighLine.color_scheme = HighLine::SampleColorScheme.new min_width = 0 @returns.each do |host, hash| min_width = host.length if host.length > min_width end buffer = '' @returns.each do |host, hash| padded_host = host.dup if hash[:exit] >= 10000 padded_host = "<%= color('#{padded_host}', :critical) %>" hash[:out] = "<%= color('#{hash[:out]}', :red) %>\n" elsif hash[:exit] > 0 padded_host = "<%= color('#{padded_host}', :error) %>" else padded_host = "<%= color('#{padded_host}', :green) %>" end padded_host << (" " * (min_width - host.length)) if host.length < min_width buffer << padded_host << "\t\t" buffer << hash[:out].lstrip end @end_time = Time.now total_nodes = @filtered_nodes.length total_error_nodes = @returns.reject { |host, hash| hash[:exit] == 0 }.length total_time = @end_time - @start_time buffer.rstrip! buffer << "\n\n" << "<%= color('#{total_nodes} host(s), #{total_error_nodes} error(s), #{seconds_in_words total_time}', " buffer << (total_error_nodes > 0 ? ":red" : ":green") << ") %>" buffer when 'json' require 'json' JSON.dump @returns when 'xml' require 'builder' xml = Builder::XmlMarkup.new(:indent => 2) xml.instruct! xml.nodes do @returns.each do |host, hash| xml.node do xml.host host xml.exit hash[:exit] xml.out hash[:out].strip end end end end end |
#init(dir) ⇒ void
Initialize the Rutty config file structure in the specified directory, or report that the files already exist there.
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/rutty/actions.rb', line 18 def init dir require 'yaml' require 'fileutils' general_file = File.join(dir, Rutty::Consts::GENERAL_CONF_FILE) nodes_file = File.join(dir, Rutty::Consts::NODES_CONF_FILE) if File.exists? dir log "\t<%= color('exists', :cyan) %>", dir else log "\t<%= color('create', :green) %>", dir FileUtils.mkdir_p dir end if File.exists? general_file log "\t<%= color('exists', :cyan) %>", general_file else log "\t<%= color('create', :green) %>", general_file defaults_hash = { :user => 'root', :keypath => File.join(ENV['HOME'], '.ssh', 'id_rsa'), :port => 22, :tags => [] } File.open(general_file, 'w') do |f| YAML.dump(defaults_hash, f) end end if File.exists? nodes_file log "\t<%= color('exists', :cyan) %>", nodes_file else log "\t<%= color('create', :green) %>", nodes_file File.open(nodes_file, 'w') do |f| YAML.dump([], f) end end end |
#list_nodes(args, options) ⇒ void
List all the user-defined nodes currently stored in the datastore file.
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 |
# File 'lib/rutty/actions.rb', line 87 def list_nodes args, check_installed! if self.nodes.empty? say "<%= color('No nodes defined', :yellow) %>" else output = case self.output_format when 'human-readable' require 'terminal-table/import' table do |t| t.headings = 'Host', 'Key', 'User', 'Port', 'Tags' self.nodes.each do |node| = node..nil? ? [] : node. t << [node.host, node.keypath, node.user, node.port, .join(', ')] end end when 'json' require 'json' JSON.dump self.nodes.collect { |node| node.to_hash } when 'xml' require 'builder' xml = Builder::XmlMarkup.new(:indent => 2) xml.instruct! xml.nodes do self.nodes.each do |node| xml.node do xml.host node.host xml.user node.user xml.port node.port xml.keypath node.keypath xml. do node..each do |tag| xml.tag tag end end end end end end puts output end end |
#scp(args, options) ⇒ void
Cycle through all the user-defined nodes, filtered by the options, connect to them and upload the specified file(s).
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/rutty/actions.rb', line 208 def scp args, @start_time = Time.now check_installed! raise Rutty::BadUsage.new "Must supply a local path and a remote path" unless args.length == 2 raise Rutty::BadUsage.new "One of -a or --tags must be passed" if .a.nil? and ..nil? raise Rutty::BadUsage.new "Use either -a or --tags, not both" if !.a.nil? and !..nil? if self.nodes.empty? say "<%= color('No nodes defined', :yellow) %>" exit end remote_path = args.pop local_path = args.pop @returns = {} @filtered_nodes = self.nodes.filter() connections = connect_to_nodes @filtered_nodes, .debug.nil? connections.each { |ssh| ssh.scp.upload! local_path, remote_path } loop do connections.delete_if { |ssh| !ssh.process(0.1) { |s| s.busy? } } break if connections.empty? end say generate_output end |