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.
- data/lib/thor-ssh/actions.rb +4 -4
- data/lib/thor-ssh/actions/file_manipulation.rb +20 -0
- data/lib/thor-ssh/local_server.rb +4 -2
- data/lib/thor-ssh/remote_file.rb +107 -6
- data/lib/thor-ssh/remote_server.rb +46 -12
- data/lib/thor-ssh/version.rb +1 -1
- data/spec/actions_spec.rb +4 -4
- data/spec/vagrant/.vagrant +1 -1
- metadata +4 -4
data/lib/thor-ssh/actions.rb
CHANGED
@@ -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,
|
80
|
-
return destination_server.run(command,
|
79
|
+
def exec(command, options={})
|
80
|
+
return destination_server.run(command, options)
|
81
81
|
end
|
82
82
|
|
83
|
-
def run(command,
|
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,
|
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,
|
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)
|
data/lib/thor-ssh/remote_file.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
62
|
-
|
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
|
-
|
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]
|
59
|
+
base.run_as_user && connection.options[:user] == base.run_as_user
|
56
60
|
end
|
57
61
|
|
58
|
-
def run(command,
|
59
|
-
|
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
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
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
|
109
|
+
return stdout_data
|
76
110
|
end
|
77
111
|
end
|
78
112
|
|
data/lib/thor-ssh/version.rb
CHANGED
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
|
|
data/spec/vagrant/.vagrant
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"active":{"default":"
|
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.
|
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-
|
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:
|
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:
|
219
|
+
hash: 543030509093831011
|
220
220
|
requirements: []
|
221
221
|
rubyforge_project:
|
222
222
|
rubygems_version: 1.8.22
|