public
Rubygem
Description: Deploy scripting with Thor using remote Ruby execution
Clone URL: git://github.com/wesabe/robot-army.git
Add support for running Ruby blocks as root using the sudo method.
Wed May 28 19:11:49 -0700 2008
commit  fbf46b3618f198e5f5a311aa11792c9d9af755cf
tree    2e3e806bcbc50e661729acfb56707aca06aac565
parent  3c80ceb546c5af749e16b23ac2fe474a280f1fcd
...
30
31
32
33
34
35
36
...
30
31
32
 
33
34
35
0
@@ -30,7 +30,6 @@ Example
0
 Known Issues
0
 ------------
0
 
0
- * No attempt is made to support `sudo` yet
0
   * Code executed in `remote` has no access to instance variables, globals, or methods on `self`
0
   * Multiple hosts are not yet supported
0
   * Probably doesn't work with Windows
...
2
3
4
 
 
5
6
7
...
2
3
4
5
6
7
8
9
0
@@ -2,6 +2,8 @@ require 'rubygems'
0
 require 'rubygems/specification'
0
 require 'thor/tasks'
0
 
0
+Dir[File.join(File.dirname(__FILE__), 'examples', '*.rb')].each {|f| require f}
0
+
0
 GEM = "robot-army"
0
 GEM_VERSION = "0.1"
0
 AUTHOR = "Brian Donovan"
...
1
2
 
3
4
 
5
 
 
6
7
8
...
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
...
123
124
125
126
127
 
 
128
129
130
...
1
 
2
3
 
4
5
6
7
8
9
10
...
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
59
60
61
62
 
63
64
65
 
66
67
68
69
...
135
136
137
 
 
138
139
140
141
142
0
@@ -1,8 +1,10 @@
0
 class RobotArmy::Connection
0
- attr_reader :host, :messenger
0
+ attr_reader :host, :user, :password, :messenger
0
   
0
- def initialize(host)
0
+ def initialize(host, user=nil, password=nil)
0
     @host = host
0
+ @user = user
0
+ @password = password
0
     @closed = true
0
   end
0
   
0
@@ -29,29 +31,39 @@ class RobotArmy::Connection
0
       ##
0
       ## bootstrap the child process
0
       ##
0
-
0
+
0
       # small hack to retain control of stdin
0
       cmd = %{ruby -rbase64 -e "eval(Base64.decode64(STDIN.gets(%(|))))"}
0
+ if user
0
+ # use sudo as root with no prompt, reading password from stdin
0
+ cmd = %{sudo -u #{@user} -p "" -S #{cmd}}
0
+ # kill the user's timestamp so that we will be asked a password
0
+ `sudo -k`
0
+ end
0
       cmd = "ssh #{host} '#{cmd}'" if host
0
-
0
- stdin, stdout, stderr = Open3.popen3 cmd
0
- stdin.sync = stdout.sync = stderr.sync = true
0
-
0
+ debug "running #{cmd}"
0
+
0
       loader.libraries.replace $TESTING ?
0
         [File.join(File.dirname(__FILE__), '..', 'robot-army')] : %w[rubygems robot-army]
0
-
0
+
0
+ stdin, stdout, stderr = Open3.popen3 cmd
0
+ stdin.sync = stdout.sync = stderr.sync = true
0
+
0
+ # give the sudo password if we got one
0
+ stdin.puts password if user && password
0
+
0
       ruby = loader.render
0
       code = Base64.encode64(ruby)
0
       stdin << code << '|'
0
-
0
-
0
+
0
+
0
       ##
0
       ## make sure it was loaded okay
0
       ##
0
-
0
+
0
       @messenger = RobotArmy::Messenger.new(stdout, stdin)
0
       response = messenger.get
0
-
0
+
0
       if response
0
         case response[:status]
0
         when 'error'
0
@@ -123,8 +135,8 @@ class RobotArmy::Connection
0
     end
0
   end
0
   
0
- def self.localhost(&block)
0
- conn = new(nil)
0
+ def self.localhost(user=nil, password=nil, &block)
0
+ conn = new(nil, user, password)
0
     block ? conn.open(&block) : conn
0
   end
0
 end
...
12
13
14
 
15
16
17
...
12
13
14
15
16
17
18
0
@@ -12,6 +12,7 @@ class RobotArmy::Loader
0
         ## setup
0
         ##
0
         
0
+ $TESTING = #{$TESTING.inspect}
0
         $stdout.sync = $stdin.sync = true
0
         #{libraries.map{|l| "require #{l.inspect}"}.join("\n")}
0
         
...
2
3
4
5
 
 
6
7
8
...
2
3
4
 
5
6
7
8
9
0
@@ -2,7 +2,8 @@ class RobotArmy::Officer < RobotArmy::Soldier
0
   def run(command, data)
0
     case command
0
     when :eval
0
- RobotArmy::Connection.localhost do |local|
0
+ debug "officer delegating eval command for user=#{data[:user].inspect}"
0
+ RobotArmy::Connection.localhost(data[:user], data[:password]) do |local|
0
         local.post(:command => command, :data => data)
0
         return RobotArmy::Connection.handle_response(local.get)
0
       end
...
14
15
16
 
17
18
19
...
14
15
16
17
18
19
20
0
@@ -14,6 +14,7 @@ class RobotArmy::Soldier
0
   end
0
   
0
   def run(command, data)
0
+ debug "#{self.class} running command=#{command.inspect}"
0
     case command
0
     when :info
0
       {:pid => Process.pid, :type => self.class.name}
...
6
7
8
9
 
 
 
 
 
10
11
12
...
17
18
19
20
 
21
22
23
...
35
36
37
 
 
 
38
39
40
41
42
43
44
45
46
47
 
48
49
50
...
53
54
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
57
...
6
7
8
 
9
10
11
12
13
14
15
16
...
21
22
23
 
24
25
26
27
...
39
40
41
42
43
44
45
46
47
48
49
 
 
 
 
 
50
51
52
53
...
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
0
@@ -6,7 +6,11 @@ module RobotArmy
0
     end
0
     
0
     def host
0
- self.class.host
0
+ @host || self.class.host
0
+ end
0
+
0
+ def host=(host)
0
+ @host = host
0
     end
0
     
0
     def say(something)
0
@@ -17,7 +21,7 @@ module RobotArmy
0
       RobotArmy::GateKeeper.shared_instance.connect(host)
0
     end
0
     
0
- def remote(host=self.host, &proc)
0
+ def remote_eval(options, &proc)
0
       ##
0
       ## build the code to send it
0
       ##
0
@@ -35,16 +39,15 @@ module RobotArmy
0
         #{proc.to_ruby(true)} # the proc itself
0
       }
0
       
0
+ options[:file] = file
0
+ options[:line] = line
0
+ options[:code] = code
0
       
0
       ##
0
       ## send the child a message
0
       ##
0
       
0
- connection.messenger.post(:command => :eval, :data => {
0
- :code => code,
0
- :file => file,
0
- :line => line
0
- })
0
+ connection.messenger.post(:command => :eval, :data => options)
0
       
0
       ##
0
       ## get and evaluate the response
0
@@ -53,5 +56,19 @@ module RobotArmy
0
       response = connection.messenger.get
0
       connection.handle_response(response)
0
     end
0
+
0
+ def ask_for_password(user)
0
+ require 'highline'
0
+ HighLine.new.ask("[sudo] #{user}@#{host||'localhost'} password: ") {|q| q.echo = false}
0
+ end
0
+
0
+ def sudo(host=self.host, &proc)
0
+ @sudo_password ||= ask_for_password('root')
0
+ remote_eval :host => host, :user => 'root', :password => @sudo_password, &proc
0
+ end
0
+
0
+ def remote(host=self.host, &proc)
0
+ remote_eval :host => host, &proc
0
+ end
0
   end
0
 end
...
51
52
53
 
 
 
 
 
 
 
 
 
54
...
51
52
53
54
55
56
57
58
59
60
61
62
63
0
@@ -51,4 +51,13 @@ describe RobotArmy::TaskMaster do
0
     info[:type].must == 'RobotArmy::Officer'
0
     @master.connection.info.must == info
0
   end
0
+
0
+ it "runs as a normal (non-super) user by default" do
0
+ @master.remote{ Process.uid }.must_not == 0
0
+ end
0
+
0
+ it "allows running as super-user" do
0
+ pending('figure out a way to run this only sometimes')
0
+ @master.sudo{ Process.uid }.must == 0
0
+ end
0
 end

Comments

    No one has commented yet.