thor-ssh 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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