thor-ssh 0.2.5 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -76,11 +76,11 @@ module ThorSsh
76
76
  end
77
77
 
78
78
  # Similar to run, but silent and always executes on the remote server
79
- def exec(command, with_codes=false)
80
- return destination_server.run(command, with_codes)
79
+ def exec(command, options={})
80
+ return destination_server.run(command, options)
81
81
  end
82
82
 
83
- def run(command, with_codes=false, config={})
83
+ def run(command, options={}, config={})
84
84
  return unless behavior == :invoke
85
85
 
86
86
  destination = relative_to_original_destination_root(destination_root, false)
@@ -95,7 +95,7 @@ module ThorSsh
95
95
 
96
96
  unless options[:pretend]
97
97
  # config[:capture] ? `#{command}` : system("#{command}")
98
- return exec(command, with_codes)
98
+ return exec(command, options)
99
99
  end
100
100
  end
101
101
 
@@ -27,6 +27,7 @@ class Thor
27
27
 
28
28
 
29
29
  def chmod(path, mode, config={})
30
+ # TODO: Check the mode first, only run if its changed
30
31
  return unless behavior == :invoke
31
32
  path = File.expand_path(path, destination_root)
32
33
  say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true)
@@ -34,6 +35,24 @@ class Thor
34
35
  destination_files.chmod(mode, path) unless options[:pretend]
35
36
  end
36
37
 
38
+ def chown(user, group, files)
39
+ unless options[:pretend]
40
+ changed, not_changed = destination_files.chown(user, group, files)
41
+
42
+ not_changed.each do |file|
43
+ say_status :chown, file
44
+ end
45
+
46
+ changed.each do |file|
47
+ say_status :chown_identical, file, :blue
48
+ end
49
+ end
50
+ end
51
+
52
+ def chown_R(user, group, files)
53
+ destination_files.chown_R(user, group, files) unless options[:pretend]
54
+ end
55
+
37
56
 
38
57
  def gsub_file(path, flag, *args, &block)
39
58
  return unless behavior == :invoke
@@ -56,6 +75,7 @@ class Thor
56
75
  say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true)
57
76
  destination_files.rm_rf(path) if !options[:pretend] && destination_files.exists?(path)
58
77
  end
78
+ alias :remove_dir :remove_file
59
79
 
60
80
 
61
81
  end
@@ -8,6 +8,8 @@ module ThorSsh
8
8
  @base = base
9
9
  end
10
10
 
11
+ # TODO: This should inherit from the same thing as RemoteServer and
12
+ # it should have the same run but different run_with_codes
11
13
  def run_with_codes(command)
12
14
  # pid, stdin, stdout, stderr = Open4::popen4(command)
13
15
  # ignored, status = Process::waitpid2 pid
@@ -27,8 +29,8 @@ module ThorSsh
27
29
  return stdout_data, stderr_data, exit_code, exit_signal
28
30
  end
29
31
 
30
- def run(command, with_codes=false)
31
- if with_codes
32
+ def run(command, options={})
33
+ if options[:with_codes]
32
34
  return run_with_codes(command)
33
35
  else
34
36
  stdout, stdin, exit_code, exit_signal = run_with_codes(command)
@@ -2,7 +2,9 @@ require 'net/ssh'
2
2
  require 'net/sftp'
3
3
  require 'stringio'
4
4
 
5
- module ThorSsh
5
+ module ThorSsh
6
+ class PermissionError < StandardError ; end
7
+
6
8
  class RemoteFile
7
9
  attr_reader :connection
8
10
  attr_reader :base
@@ -34,10 +36,19 @@ module ThorSsh
34
36
  def run(command)
35
37
  return base.exec(command)
36
38
  end
39
+
40
+ def exec(command, options={})
41
+ return base.exec(command, options)
42
+ end
37
43
 
38
44
  # Creates the directory at the path on the remote server
39
45
  def mkdir_p(path)
40
- run "mkdir -p \"#{path}\""
46
+ stdout, stderr, _, _ = exec("mkdir -p #{path.inspect}", :with_codes => true)
47
+
48
+ if stderr =~ /Permission denied/
49
+ base.say_status :permission_error, stderr, :red
50
+ raise PermissionError, "unable to create directory #{path}"
51
+ end
41
52
  end
42
53
 
43
54
  # Remote the file/folder on the remote server
@@ -58,9 +69,17 @@ module ThorSsh
58
69
  # If we first logged in as the running user
59
70
  if base.destination_server.running_as_current_user?
60
71
  data = nil
61
- connection.sftp.file.open(path, "rb") do |f|
62
- data = f.read
72
+ begin
73
+ # Attempt to upload to the path
74
+ data = connection.sftp.upload!(path)
75
+ rescue Net::SFTP::StatusException => e
76
+ close_sftp!
77
+ raise PermissionError, "Unable to download #{path}"
63
78
  end
79
+
80
+ # connection.sftp.file.open(path, "rb") do |f|
81
+ # data = f.read
82
+ # end
64
83
  close_sftp!
65
84
  else
66
85
  # We just run this as root, when reading we don't need to go back
@@ -73,8 +92,67 @@ module ThorSsh
73
92
 
74
93
  # TODO: we should just move this to a more standard thing
75
94
  def binwrite(path, data)
95
+ data = data.gsub(/\n\r/, "\n").gsub(/\r\n/, "\n")
96
+
76
97
  io = StringIO.new(data)
77
- connection.sftp.upload!(io, path)
98
+ begin
99
+ # Attempt to upload to the path
100
+ connection.sftp.upload!(io, path)
101
+ rescue Net::SFTP::StatusException => e
102
+ # In the event we don't have permission, upload to
103
+ # a temp dir, then copy with sudo
104
+ close_sftp!
105
+
106
+ temp_dir = nil
107
+ base.as_user(nil) do
108
+ current_dir = run("pwd").strip
109
+ temp_dir = "#{current_dir}/.thorssh"
110
+ # Make the dir as the default user
111
+ run("mkdir -p \"#{temp_dir}\"")
112
+ end
113
+
114
+ temp_file = "#{temp_dir}/#{File.basename(path)}"
115
+
116
+ # io = StringIO.new(data)
117
+ io = StringIO.new(data)
118
+ connection.sftp.upload!(io, temp_file)
119
+ close_sftp!
120
+
121
+
122
+ user = base.run_as_user
123
+
124
+ # Move the file as the user
125
+ base.as_root do
126
+ folder_path = File.dirname(path)
127
+ unless base.destination_files.exists?(folder_path)
128
+ # Create the directory this is supposed to transfer into
129
+ mkdir_p(folder_path)
130
+
131
+ # And set the correct user/group
132
+ if user
133
+ chown(user, user, folder_path)
134
+ end
135
+ end
136
+
137
+ # Move the file
138
+ run("mv #{temp_file.inspect} #{path.inspect}")
139
+ end
140
+
141
+ unless base.destination_files.exists?(path)
142
+ # Something went wrong
143
+ raise PermissionError, "#{path} failed to create/update failed"
144
+ end
145
+
146
+ if user
147
+ # Set the correct user as if this user had uploaded
148
+ # directly.
149
+ base.as_root do
150
+ chown(user, user, path)
151
+ end
152
+ end
153
+
154
+ return
155
+ end
78
156
  close_sftp!
79
157
  end
80
158
 
@@ -87,6 +165,30 @@ module ThorSsh
87
165
  return run("chmod #{mode} \"#{file_name}\"")
88
166
  end
89
167
 
168
+ def chown(user, group, list, options={})
169
+ changed = []
170
+ not_changed = []
171
+ recursive_flag = options[:recursive] ? '-R' : ''
172
+ [list].flatten.each do |file|
173
+ _, _, file_user, file_group = exec("ls -lh #{file.inspect}").split(/\n/).reject {|l| l =~ /^total/ }.last.split(/\s/)
174
+ if user == file_user && group == file_group
175
+ not_changed << file
176
+ else
177
+ changed << file
178
+ base.as_root do
179
+ # We can just always run this as root for the user
180
+ exec("chown #{recursive_flag} #{user}:#{group} #{file.inspect}")
181
+ end
182
+ end
183
+ end
184
+
185
+ return changed, not_changed
186
+ end
187
+
188
+ def chown_R(user, group, list)
189
+ return chown(user, group, list, :recursive => true)
190
+ end
191
+
90
192
  def inode(file_name)
91
193
  return run("ls -i \"#{file_name}\"").strip.split(/ /).first
92
194
  end
@@ -95,6 +197,5 @@ module ThorSsh
95
197
  def identical?(file1, file2)
96
198
  inode(file1) == inode(file2)
97
199
  end
98
-
99
200
  end
100
201
  end
@@ -11,12 +11,15 @@ module ThorSsh
11
11
  @connection = connection
12
12
  end
13
13
 
14
- def run_with_codes(command)
14
+ def run_with_codes(command, options)
15
15
  stdout_data = ""
16
16
  stderr_data = ""
17
17
  exit_code = nil
18
18
  exit_signal = nil
19
19
  channel = connection.open_channel do |cha|
20
+ # TODO: Lets do more research on request_pty
21
+ # It fixes the bug with "stdin: is not a tty"
22
+ channel.request_pty unless options[:no_pty]
20
23
  cha.exec(command) do |ch, success|
21
24
  unless success
22
25
  abort "FAILED: couldn't execute command (connection.channel.exec)"
@@ -42,37 +45,68 @@ module ThorSsh
42
45
  # channel.close
43
46
  # end
44
47
  end
45
- # channel.wait
46
48
  # puts "Done Loop"
47
49
  # channel.close
48
50
  end
49
51
  connection.loop
50
-
52
+
53
+ # puts "OUTPUT: #{[stdout_data, stderr_data, exit_code, exit_signal].inspect}"
54
+
51
55
  return stdout_data, stderr_data, exit_code, exit_signal
52
56
  end
53
57
 
54
58
  def running_as_current_user?
55
- base.run_as_user && connection.options[:user] != base.run_as_user
59
+ base.run_as_user && connection.options[:user] == base.run_as_user
56
60
  end
57
61
 
58
- def run(command, with_codes=false)
59
- if running_as_current_user?
62
+ def run(command, options={})
63
+ options[:with_codes] ||= false
64
+ options[:log_stderr] = true unless options.has_key?(:log_stderr)
65
+ # Runs the command with the correct sudo's to get it to the current
66
+ # user. You can also do as_user(nil) do ... to get to the login
67
+ # user.
68
+
69
+ puts command
70
+
71
+ # A few notes on running commands as a different user
72
+ # 1) we use -i to get it as if you had logged in directly
73
+ # as the other user
74
+ # 2) we use bash -c to run it all in bash so things like rediects
75
+ # and multiple commands work
76
+ if !running_as_current_user? && base.run_as_user != nil
60
77
  # We need to change to a different user
61
78
  if base.run_as_user == 'root'
62
79
  # We need to go up to root
63
- command = "sudo #{command}"
80
+ # TODO: We don't need to run in bash if its not going to pipe
81
+ command = "sudo -i bash -c #{command.inspect}"
64
82
  else
65
83
  # We need to go up to root, then down to this user
66
84
  # This involves running sudo (to go up to root), then running
67
85
  # sudo again as the new user, then running the command
68
- command = "sudo sudo -u #{base.run_as_user} #{command}"
86
+ command = "sudo sudo -u #{base.run_as_user} -i bash -c #{command.inspect}"
69
87
  end
70
88
  end
71
- results = run_with_codes(command)
72
- if with_codes
73
- return results
89
+ stdout_data, stderr_data, exit_code, exit_signal = run_with_codes(command, options)
90
+
91
+ # if stderr_data.strip != ''
92
+ if exit_code != 0
93
+ base.say "#{exit_code}>> #{command}", :red
94
+ base.say stderr_data, :red
95
+ end
96
+
97
+ unless options[:keep_colors]
98
+ stdout_data = stdout_data.gsub(/\e\[(\d+)m/, '')
99
+ stderr_data = stderr_data.gsub(/\e\[(\d+)m/, '')
100
+ end
101
+
102
+ # Remove \r's
103
+ stdout_data = stdout_data.gsub(/\r\n/, "\n").gsub(/\n\r/, "\n").gsub(/\r/, "\n") if stdout_data
104
+ stderr_data = stderr_data.gsub(/\r\n/, "\n").gsub(/\n\r/, "\n").gsub(/\r/, "\n") if stderr_data
105
+
106
+ if options[:with_codes]
107
+ return stdout_data, stderr_data, exit_code, exit_signal
74
108
  else
75
- return results.first
109
+ return stdout_data
76
110
  end
77
111
  end
78
112
 
@@ -1,3 +1,3 @@
1
1
  module ThorSsh
2
- VERSION = "0.2.5"
2
+ VERSION = "0.2.6"
3
3
  end
data/spec/actions_spec.rb CHANGED
@@ -168,18 +168,18 @@ describe "Thor SSH" do
168
168
  end
169
169
 
170
170
  it "should run exec with an exit code remotely" do
171
- stdout, stderr, exit_code, exit_signal = @remote_test.exec('false', true)
171
+ stdout, stderr, exit_code, exit_signal = @remote_test.exec('false', :with_codes => true)
172
172
  exit_code.should == 1
173
173
 
174
- stdout, stderr, exit_code, exit_signal = @remote_test.exec('true', true)
174
+ stdout, stderr, exit_code, exit_signal = @remote_test.exec('true', :with_codes => true)
175
175
  exit_code.should == 0
176
176
  end
177
177
 
178
178
  it "should run exec with an exit code locally" do
179
- stdout, stderr, exit_code, exit_signal = @local_test.exec('false', true)
179
+ stdout, stderr, exit_code, exit_signal = @local_test.exec('false', :with_codes => true)
180
180
  exit_code.should == 1
181
181
 
182
- stdout, stderr, exit_code, exit_signal = @local_test.exec('true', true)
182
+ stdout, stderr, exit_code, exit_signal = @local_test.exec('true', :with_codes => true)
183
183
  exit_code.should == 0
184
184
  end
185
185
 
@@ -1 +1 @@
1
- {"active":{"default":"11f0dbac-afd7-43ce-b6d8-7c10268c68f7"}}
1
+ {"active":{"default":"d6c832ef-65a2-45d3-9f87-3a28ce460b04"}}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thor-ssh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.2.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-15 00:00:00.000000000 Z
12
+ date: 2012-06-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
@@ -207,7 +207,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
207
207
  version: '0'
208
208
  segments:
209
209
  - 0
210
- hash: -3424361872212654297
210
+ hash: 543030509093831011
211
211
  required_rubygems_version: !ruby/object:Gem::Requirement
212
212
  none: false
213
213
  requirements:
@@ -216,7 +216,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
216
216
  version: '0'
217
217
  segments:
218
218
  - 0
219
- hash: -3424361872212654297
219
+ hash: 543030509093831011
220
220
  requirements: []
221
221
  rubyforge_project:
222
222
  rubygems_version: 1.8.22