Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add support for running Ruby blocks as root using the sudo method.

  • Loading branch information...
commit fbf46b3618f198e5f5a311aa11792c9d9af755cf 1 parent 3c80ceb
Brian Donovan authored
View
1  README.markdown
@@ -30,7 +30,6 @@ Example
Known Issues
------------
- * No attempt is made to support `sudo` yet
* Code executed in `remote` has no access to instance variables, globals, or methods on `self`
* Multiple hosts are not yet supported
* Probably doesn't work with Windows
View
2  Thorfile
@@ -2,6 +2,8 @@ require 'rubygems'
require 'rubygems/specification'
require 'thor/tasks'
+Dir[File.join(File.dirname(__FILE__), 'examples', '*.rb')].each {|f| require f}
+
GEM = "robot-army"
GEM_VERSION = "0.1"
AUTHOR = "Brian Donovan"
View
13 examples/whoami.rb
@@ -0,0 +1,13 @@
+#!/usr/bin/env ruby
+require File.join(File.dirname(__FILE__), '..', 'lib', 'robot-army')
+
+class Whoami < RobotArmy::TaskMaster
+ desc 'test', "Tests whoami"
+ method_options :root => :boolean, :host => :optional
+ def test(options={})
+ self.host = options['host']
+ puts options['root'] ?
+ sudo{ `whoami` } :
+ remote{ `whoami` }
+ end
+end
View
40 lib/robot-army/connection.rb
@@ -1,8 +1,10 @@
class RobotArmy::Connection
- attr_reader :host, :messenger
+ attr_reader :host, :user, :password, :messenger
- def initialize(host)
+ def initialize(host, user=nil, password=nil)
@host = host
+ @user = user
+ @password = password
@closed = true
end
@@ -29,29 +31,39 @@ def start_child
##
## bootstrap the child process
##
-
+
# small hack to retain control of stdin
cmd = %{ruby -rbase64 -e "eval(Base64.decode64(STDIN.gets(%(|))))"}
+ if user
+ # use sudo as root with no prompt, reading password from stdin
+ cmd = %{sudo -u #{@user} -p "" -S #{cmd}}
+ # kill the user's timestamp so that we will be asked a password
+ `sudo -k`
+ end
cmd = "ssh #{host} '#{cmd}'" if host
-
- stdin, stdout, stderr = Open3.popen3 cmd
- stdin.sync = stdout.sync = stderr.sync = true
-
+ debug "running #{cmd}"
+
loader.libraries.replace $TESTING ?
[File.join(File.dirname(__FILE__), '..', 'robot-army')] : %w[rubygems robot-army]
-
+
+ stdin, stdout, stderr = Open3.popen3 cmd
+ stdin.sync = stdout.sync = stderr.sync = true
+
+ # give the sudo password if we got one
+ stdin.puts password if user && password
+
ruby = loader.render
code = Base64.encode64(ruby)
stdin << code << '|'
-
-
+
+
##
## make sure it was loaded okay
##
-
+
@messenger = RobotArmy::Messenger.new(stdout, stdin)
response = messenger.get
-
+
if response
case response[:status]
when 'error'
@@ -123,8 +135,8 @@ def self.handle_response(response)
end
end
- def self.localhost(&block)
- conn = new(nil)
+ def self.localhost(user=nil, password=nil, &block)
+ conn = new(nil, user, password)
block ? conn.open(&block) : conn
end
end
View
1  lib/robot-army/loader.rb
@@ -12,6 +12,7 @@ def render
## setup
##
+ $TESTING = #{$TESTING.inspect}
$stdout.sync = $stdin.sync = true
#{libraries.map{|l| "require #{l.inspect}"}.join("\n")}
View
3  lib/robot-army/officer.rb
@@ -2,7 +2,8 @@ class RobotArmy::Officer < RobotArmy::Soldier
def run(command, data)
case command
when :eval
- RobotArmy::Connection.localhost do |local|
+ debug "officer delegating eval command for user=#{data[:user].inspect}"
+ RobotArmy::Connection.localhost(data[:user], data[:password]) do |local|
local.post(:command => command, :data => data)
return RobotArmy::Connection.handle_response(local.get)
end
View
1  lib/robot-army/soldier.rb
@@ -14,6 +14,7 @@ def listen
end
def run(command, data)
+ debug "#{self.class} running command=#{command.inspect}"
case command
when :info
{:pid => Process.pid, :type => self.class.name}
View
31 lib/robot-army/task_master.rb
@@ -6,7 +6,11 @@ def self.host(host=nil)
end
def host
- self.class.host
+ @host || self.class.host
+ end
+
+ def host=(host)
+ @host = host
end
def say(something)
@@ -17,7 +21,7 @@ def connection
RobotArmy::GateKeeper.shared_instance.connect(host)
end
- def remote(host=self.host, &proc)
+ def remote_eval(options, &proc)
##
## build the code to send it
##
@@ -35,16 +39,15 @@ def remote(host=self.host, &proc)
#{proc.to_ruby(true)} # the proc itself
}
+ options[:file] = file
+ options[:line] = line
+ options[:code] = code
##
## send the child a message
##
- connection.messenger.post(:command => :eval, :data => {
- :code => code,
- :file => file,
- :line => line
- })
+ connection.messenger.post(:command => :eval, :data => options)
##
## get and evaluate the response
@@ -53,5 +56,19 @@ def remote(host=self.host, &proc)
response = connection.messenger.get
connection.handle_response(response)
end
+
+ def ask_for_password(user)
+ require 'highline'
+ HighLine.new.ask("[sudo] #{user}@#{host||'localhost'} password: ") {|q| q.echo = false}
+ end
+
+ def sudo(host=self.host, &proc)
+ @sudo_password ||= ask_for_password('root')
+ remote_eval :host => host, :user => 'root', :password => @sudo_password, &proc
+ end
+
+ def remote(host=self.host, &proc)
+ remote_eval :host => host, &proc
+ end
end
end
View
9 spec/task_master_spec.rb
@@ -51,4 +51,13 @@ class Localhost < RobotArmy::TaskMaster
info[:type].must == 'RobotArmy::Officer'
@master.connection.info.must == info
end
+
+ it "runs as a normal (non-super) user by default" do
+ @master.remote{ Process.uid }.must_not == 0
+ end
+
+ it "allows running as super-user" do
+ pending('figure out a way to run this only sometimes')
+ @master.sudo{ Process.uid }.must == 0
+ end
end
Please sign in to comment.
Something went wrong with that request. Please try again.