nucleon 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +7 -3
- data/Gemfile.lock +10 -6
- data/VERSION +1 -1
- data/lib/core/config.rb +9 -5
- data/lib/core/errors.rb +6 -0
- data/lib/core/facade.rb +12 -1
- data/lib/core/manager.rb +23 -13
- data/lib/core/mixin/macro/object_interface.rb +42 -36
- data/lib/core/mixin/macro/plugin_interface.rb +13 -18
- data/lib/core/plugin/action.rb +6 -4
- data/lib/core/plugin/base.rb +3 -3
- data/lib/core/plugin/command.rb +1 -1
- data/lib/core/plugin/project.rb +31 -13
- data/lib/core/util/cli.rb +7 -7
- data/lib/core/util/data.rb +3 -2
- data/lib/core/util/disk.rb +4 -1
- data/lib/core/util/shell.rb +15 -11
- data/lib/core/util/ssh.rb +356 -0
- data/lib/nucleon/command/bash.rb +1 -1
- data/lib/nucleon/event/regex.rb +1 -1
- data/lib/nucleon/project/git.rb +3 -3
- data/lib/nucleon/project/github.rb +6 -11
- data/lib/nucleon_base.rb +8 -2
- data/nucleon.gemspec +12 -8
- metadata +55 -43
data/lib/core/plugin/action.rb
CHANGED
@@ -74,7 +74,7 @@ class Action < Base
|
|
74
74
|
|
75
75
|
#---
|
76
76
|
|
77
|
-
def normalize
|
77
|
+
def normalize(reload)
|
78
78
|
args = array(delete(:args, []))
|
79
79
|
|
80
80
|
@action_interface = Util::Liquid.new do |method, method_args|
|
@@ -101,7 +101,7 @@ class Action < Base
|
|
101
101
|
set(:settings, Config.new)
|
102
102
|
configure
|
103
103
|
parse_base(args)
|
104
|
-
end
|
104
|
+
end
|
105
105
|
end
|
106
106
|
|
107
107
|
#-----------------------------------------------------------------------------
|
@@ -303,11 +303,13 @@ class Action < Base
|
|
303
303
|
# Validate all of the configurations
|
304
304
|
success = true
|
305
305
|
config.export.each do |name, option|
|
306
|
-
|
306
|
+
unless ignore.include?(name)
|
307
|
+
success = false unless option.validate(settings[name], *args)
|
308
|
+
end
|
307
309
|
end
|
308
310
|
if success
|
309
311
|
# Check for missing arguments (in case of internal execution mode)
|
310
|
-
arguments.each do |name|
|
312
|
+
arguments.each do |name|
|
311
313
|
if settings[name.to_sym].nil?
|
312
314
|
warn('nucleon.core.exec.errors.missing_argument', { :name => name })
|
313
315
|
success = false
|
data/lib/core/plugin/base.rb
CHANGED
@@ -6,7 +6,7 @@ class Base < Core
|
|
6
6
|
# All Plugin classes should directly or indirectly extend Base
|
7
7
|
|
8
8
|
def initialize(type, provider, options)
|
9
|
-
config = Util::Data.clean(Config.ensure(options))
|
9
|
+
config = Util::Data.clean(Config.ensure(options), false)
|
10
10
|
name = Util::Data.ensure_value(config.delete(:plugin_name), config.delete(:name, provider))
|
11
11
|
|
12
12
|
@quiet = config.delete(:quiet, false)
|
@@ -18,7 +18,7 @@ class Base < Core
|
|
18
18
|
myself.plugin_name = name
|
19
19
|
|
20
20
|
logger.debug("Normalizing #{plugin_type} plugin #{plugin_name} with meta data: #{meta.inspect}")
|
21
|
-
normalize
|
21
|
+
normalize(false)
|
22
22
|
end
|
23
23
|
|
24
24
|
#---
|
@@ -147,7 +147,7 @@ class Base < Core
|
|
147
147
|
#-----------------------------------------------------------------------------
|
148
148
|
# Plugin operations
|
149
149
|
|
150
|
-
def normalize
|
150
|
+
def normalize(reload)
|
151
151
|
# Implement in sub classes
|
152
152
|
end
|
153
153
|
|
data/lib/core/plugin/command.rb
CHANGED
data/lib/core/plugin/project.rb
CHANGED
@@ -36,21 +36,26 @@ class Project < Base
|
|
36
36
|
#-----------------------------------------------------------------------------
|
37
37
|
# Project plugin interface
|
38
38
|
|
39
|
-
def normalize
|
39
|
+
def normalize(reload)
|
40
40
|
super
|
41
41
|
|
42
|
-
extension(:normalize)
|
43
|
-
|
44
42
|
set_directory(Util::Disk.filename(get(:directory, Dir.pwd)))
|
43
|
+
register
|
44
|
+
|
45
45
|
set_url(get(:url)) if get(:url, false)
|
46
46
|
|
47
47
|
myself.plugin_name = path if myself.plugin_name == plugin_provider
|
48
48
|
|
49
|
+
ui.resource = plugin_name
|
50
|
+
logger = plugin_name
|
51
|
+
|
49
52
|
if keys = delete(:keys, nil)
|
50
53
|
set(:private_key, keys[:private_key])
|
51
54
|
set(:public_key, keys[:public_key])
|
52
55
|
end
|
53
56
|
|
57
|
+
extension(:normalize)
|
58
|
+
|
54
59
|
init_project
|
55
60
|
extension(:init)
|
56
61
|
|
@@ -63,7 +68,7 @@ class Project < Base
|
|
63
68
|
init_auth
|
64
69
|
init_parent
|
65
70
|
init_remotes
|
66
|
-
load_revision
|
71
|
+
load_revision
|
67
72
|
end
|
68
73
|
|
69
74
|
#-----------------------------------------------------------------------------
|
@@ -71,7 +76,12 @@ class Project < Base
|
|
71
76
|
|
72
77
|
def register
|
73
78
|
super
|
74
|
-
|
79
|
+
if directory
|
80
|
+
lib_path = File.join(directory, 'lib')
|
81
|
+
if File.directory?(lib_path)
|
82
|
+
CORL.register(lib_path)
|
83
|
+
end
|
84
|
+
end
|
75
85
|
end
|
76
86
|
|
77
87
|
#-----------------------------------------------------------------------------
|
@@ -560,7 +570,7 @@ class Project < Base
|
|
560
570
|
|
561
571
|
#---
|
562
572
|
|
563
|
-
def
|
573
|
+
def each
|
564
574
|
if can_persist?
|
565
575
|
localize do
|
566
576
|
logger.info("Iterating through all sub projects of project #{name}")
|
@@ -614,14 +624,20 @@ class Project < Base
|
|
614
624
|
|
615
625
|
#---
|
616
626
|
|
617
|
-
def set_remote(name, url)
|
627
|
+
def set_remote(name, url, options = {})
|
628
|
+
config = Config.ensure(options)
|
629
|
+
|
618
630
|
if can_persist?
|
619
631
|
localize do
|
620
|
-
|
621
|
-
|
632
|
+
unless url.strip.empty?
|
633
|
+
if url = extension_set(:set_remote, url, { :name => name })
|
634
|
+
delete_remote(name)
|
635
|
+
|
636
|
+
url = translate_edit_url(url) if name == :edit && config.get(:translate, true)
|
622
637
|
|
623
|
-
|
624
|
-
|
638
|
+
logger.info("Setting project remote #{name} to #{url}")
|
639
|
+
yield(url) if block_given?
|
640
|
+
end
|
625
641
|
end
|
626
642
|
end
|
627
643
|
else
|
@@ -637,6 +653,8 @@ class Project < Base
|
|
637
653
|
config = Config.ensure(options)
|
638
654
|
|
639
655
|
if url = extension_set(:add_remote_url, url, { :name => name, :config => config })
|
656
|
+
url = translate_edit_url(url) if name == :edit && config.get(:translate, true)
|
657
|
+
|
640
658
|
logger.info("Adding project remote url #{url} to #{name}")
|
641
659
|
yield(config, url) if block_given?
|
642
660
|
end
|
@@ -651,7 +669,7 @@ class Project < Base
|
|
651
669
|
def set_host_remote(name, hosts, path, options = {})
|
652
670
|
if can_persist?
|
653
671
|
localize do
|
654
|
-
config = Config.ensure(options).import({ :path => path })
|
672
|
+
config = Config.ensure(options).import({ :path => path, :translate => false })
|
655
673
|
hosts = array(hosts)
|
656
674
|
|
657
675
|
unless hosts.empty?
|
@@ -660,7 +678,7 @@ class Project < Base
|
|
660
678
|
path = config.delete(:path)
|
661
679
|
|
662
680
|
logger.info("Setting host remote #{name} for #{hosts.inspect} at #{path}")
|
663
|
-
set_remote(name, translate_url(hosts.shift, path, config.export))
|
681
|
+
set_remote(name, translate_url(hosts.shift, path, config.export), config)
|
664
682
|
|
665
683
|
hosts.each do |host|
|
666
684
|
logger.debug("Adding remote url to #{host}")
|
data/lib/core/util/cli.rb
CHANGED
@@ -16,11 +16,11 @@ module CLI
|
|
16
16
|
#---
|
17
17
|
|
18
18
|
def self.encode(data)
|
19
|
-
Base64.
|
19
|
+
Base64.urlsafe_encode64(Util::Data.to_json(data, false))
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.decode(encoded_string)
|
23
|
-
Util::Data.symbol_map(Util::Data.parse_json(Base64.
|
23
|
+
Util::Data.symbol_map(Util::Data.parse_json(Base64.urlsafe_decode64(encoded_string)))
|
24
24
|
end
|
25
25
|
|
26
26
|
#-------------------------------------------------------------------------
|
@@ -75,7 +75,7 @@ module CLI
|
|
75
75
|
if sub_command
|
76
76
|
results << [ sub_command, sub_args ]
|
77
77
|
end
|
78
|
-
|
78
|
+
|
79
79
|
return results.flatten
|
80
80
|
end
|
81
81
|
|
@@ -130,14 +130,14 @@ module CLI
|
|
130
130
|
options[:help] = true
|
131
131
|
end
|
132
132
|
end
|
133
|
-
|
134
133
|
parser.parse!(args)
|
135
134
|
|
136
|
-
# Now we can act on options given
|
137
|
-
Nucleon.log_level = options[:log_level] if options[:log_level]
|
135
|
+
# Now we can act on options given
|
138
136
|
return if options[:help]
|
139
137
|
|
140
138
|
parse_encoded
|
139
|
+
|
140
|
+
Nucleon.log_level = options[:log_level] if options[:log_level]
|
141
141
|
|
142
142
|
remaining_args = args.dup
|
143
143
|
arg_messages = []
|
@@ -217,7 +217,7 @@ module CLI
|
|
217
217
|
end
|
218
218
|
|
219
219
|
encoded_properties.each do |name, value|
|
220
|
-
self.options[name] = value
|
220
|
+
self.options[name] = value
|
221
221
|
end
|
222
222
|
end
|
223
223
|
options.delete(:encoded_params)
|
data/lib/core/util/data.rb
CHANGED
@@ -234,9 +234,10 @@ class Data
|
|
234
234
|
#-----------------------------------------------------------------------------
|
235
235
|
# Operations
|
236
236
|
|
237
|
-
def self.clean(data)
|
237
|
+
def self.clean(data, remove_empty = true)
|
238
238
|
data.keys.each do |key|
|
239
|
-
|
239
|
+
obj = data[key]
|
240
|
+
data.delete(key) if obj.nil? || ( remove_empty && obj.is_a?(Hash) && obj.empty? )
|
240
241
|
end
|
241
242
|
data
|
242
243
|
end
|
data/lib/core/util/disk.rb
CHANGED
data/lib/core/util/shell.rb
CHANGED
@@ -91,20 +91,22 @@ class Shell < Core
|
|
91
91
|
begin
|
92
92
|
t1, output_new, output_orig, output_reader = pipe_exec_stream($stdout, conditions, {
|
93
93
|
:prefix => info_prefix,
|
94
|
-
:suffix => info_suffix,
|
94
|
+
:suffix => info_suffix,
|
95
|
+
:quiet => config.get(:quiet, false)
|
95
96
|
}, 'output') do |data|
|
96
97
|
system_result.append_output(data)
|
97
|
-
|
98
|
+
code ? code.call(:output, command, data) : true
|
98
99
|
end
|
99
100
|
|
100
101
|
t2, error_new, error_orig, error_reader = pipe_exec_stream($stderr, conditions, {
|
101
102
|
:prefix => error_prefix,
|
102
|
-
:suffix => error_suffix,
|
103
|
+
:suffix => error_suffix,
|
104
|
+
:quiet => config.get(:quiet, false)
|
103
105
|
}, 'error') do |data|
|
104
106
|
system_result.append_errors(data)
|
105
|
-
|
107
|
+
code ? code.call(:error, command, data) : true
|
106
108
|
end
|
107
|
-
|
109
|
+
|
108
110
|
system_success = system(command)
|
109
111
|
system_result.status = $?.exitstatus
|
110
112
|
|
@@ -131,7 +133,7 @@ class Shell < Core
|
|
131
133
|
|
132
134
|
thread = process_stream(read, original, options, label) do |data|
|
133
135
|
check_conditions(data, conditions, match_prefix) do
|
134
|
-
|
136
|
+
code ? code.call(data) : true
|
135
137
|
end
|
136
138
|
end
|
137
139
|
|
@@ -170,7 +172,7 @@ class Shell < Core
|
|
170
172
|
end
|
171
173
|
|
172
174
|
result = true
|
173
|
-
if
|
175
|
+
if code
|
174
176
|
result = code.call
|
175
177
|
|
176
178
|
unless prefix.empty?
|
@@ -206,7 +208,7 @@ class Shell < Core
|
|
206
208
|
suffix = default_suffix
|
207
209
|
|
208
210
|
unless line.empty?
|
209
|
-
if
|
211
|
+
if code
|
210
212
|
result = code.call(line)
|
211
213
|
|
212
214
|
if result && result.is_a?(Hash)
|
@@ -217,11 +219,13 @@ class Shell < Core
|
|
217
219
|
success = result if success
|
218
220
|
end
|
219
221
|
|
220
|
-
prefix = ( prefix && ! prefix.empty? ?
|
222
|
+
prefix = ( prefix && ! prefix.empty? ? prefix : '' )
|
221
223
|
suffix = ( suffix && ! suffix.empty? ? suffix : '' )
|
222
224
|
eol = ( index < lines.length - 1 || newline ? "\n" : ' ' )
|
223
|
-
|
224
|
-
|
225
|
+
|
226
|
+
unless options[:quiet]
|
227
|
+
output.write(prefix.lstrip + line + suffix.rstrip + eol)
|
228
|
+
end
|
225
229
|
end
|
226
230
|
end
|
227
231
|
end
|
@@ -0,0 +1,356 @@
|
|
1
|
+
|
2
|
+
module Nucleon
|
3
|
+
module Util
|
4
|
+
class SSH < Core
|
5
|
+
|
6
|
+
#-----------------------------------------------------------------------------
|
7
|
+
# User key home
|
8
|
+
|
9
|
+
@@key_path = nil
|
10
|
+
|
11
|
+
#---
|
12
|
+
|
13
|
+
def self.key_path
|
14
|
+
unless @@key_path
|
15
|
+
home_path = ( ENV['USER'] == 'root' ? '/root' : ENV['HOME'] ) # In case we are using sudo
|
16
|
+
@@key_path = File.join(home_path, '.ssh')
|
17
|
+
|
18
|
+
FileUtils.mkdir(@@key_path) unless File.directory?(@@key_path)
|
19
|
+
end
|
20
|
+
@@key_path
|
21
|
+
end
|
22
|
+
|
23
|
+
#-----------------------------------------------------------------------------
|
24
|
+
# Instance generators
|
25
|
+
|
26
|
+
def self.generate(options = {})
|
27
|
+
config = Config.ensure(options)
|
28
|
+
|
29
|
+
private_key = config.get(:private_key, nil)
|
30
|
+
original_key = nil
|
31
|
+
key_comment = config.get(:comment, '')
|
32
|
+
|
33
|
+
if private_key.nil?
|
34
|
+
key_type = config.get(:type, "RSA")
|
35
|
+
key_bits = config.get(:bits, 2048)
|
36
|
+
passphrase = config.get(:passphrase, nil)
|
37
|
+
|
38
|
+
key_data = SSHKey.generate(
|
39
|
+
:type => key_type,
|
40
|
+
:bits => key_bits,
|
41
|
+
:comment => key_comment,
|
42
|
+
:passphrase => passphrase
|
43
|
+
)
|
44
|
+
is_new = true
|
45
|
+
|
46
|
+
else
|
47
|
+
if private_key.include?('PRIVATE KEY')
|
48
|
+
original_key = private_key
|
49
|
+
else
|
50
|
+
original_key = Disk.read(private_key)
|
51
|
+
end
|
52
|
+
|
53
|
+
key_data = SSHKey.new(original_key, :comment => key_comment) if original_key
|
54
|
+
is_new = false
|
55
|
+
end
|
56
|
+
|
57
|
+
return nil unless key_data && ! key_data.ssh_public_key.empty?
|
58
|
+
Keypair.new(key_data, is_new, original_key)
|
59
|
+
end
|
60
|
+
|
61
|
+
#-----------------------------------------------------------------------------
|
62
|
+
# Checks
|
63
|
+
|
64
|
+
def self.valid?(public_ssh_key)
|
65
|
+
SSHKey.valid_ssh_public_key?(public_ssh_key)
|
66
|
+
end
|
67
|
+
|
68
|
+
#-----------------------------------------------------------------------------
|
69
|
+
# Keypair interface
|
70
|
+
|
71
|
+
class Keypair
|
72
|
+
attr_reader :type, :private_key, :encrypted_key, :public_key, :ssh_key
|
73
|
+
|
74
|
+
def initialize(key_data, is_new, original_key)
|
75
|
+
@type = key_data.type
|
76
|
+
@private_key = key_data.private_key
|
77
|
+
@encrypted_key = is_new ? key_data.encrypted_private_key : original_key
|
78
|
+
@public_key = key_data.public_key
|
79
|
+
@ssh_key = key_data.ssh_public_key
|
80
|
+
end
|
81
|
+
|
82
|
+
#---
|
83
|
+
|
84
|
+
def store(key_path = nil, key_base = 'id')
|
85
|
+
key_path = SSH.key_path if key_path.nil?
|
86
|
+
private_key_file = File.join(key_path, "#{key_base}_#{type.downcase}")
|
87
|
+
public_key_file = File.join(key_path, "#{key_base}_#{type.downcase}.pub")
|
88
|
+
|
89
|
+
private_success = Disk.write(private_key_file, encrypted_key)
|
90
|
+
FileUtils.chmod(0600, private_key_file) if private_success
|
91
|
+
|
92
|
+
public_success = Disk.write(public_key_file, ssh_key)
|
93
|
+
|
94
|
+
if private_success && public_success
|
95
|
+
return { :private_key => private_key_file, :public_key => public_key_file }
|
96
|
+
end
|
97
|
+
false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
#-----------------------------------------------------------------------------
|
102
|
+
# SSH Execution interface
|
103
|
+
|
104
|
+
@@sessions = {}
|
105
|
+
|
106
|
+
#---
|
107
|
+
|
108
|
+
def self.session_id(hostname, user)
|
109
|
+
"#{hostname}-#{user}"
|
110
|
+
end
|
111
|
+
|
112
|
+
#---
|
113
|
+
|
114
|
+
def self.session(hostname, user, port = 22, private_key = nil, reset = false, options = {})
|
115
|
+
require 'net/ssh'
|
116
|
+
|
117
|
+
ssh_options = Config.new({
|
118
|
+
:user_known_hosts_file => [ File.join(key_path, 'known_hosts'), File.join(key_path, 'known_hosts2') ],
|
119
|
+
:key_data => [],
|
120
|
+
:keys_only => false,
|
121
|
+
:auth_methods => [ 'publickey' ],
|
122
|
+
:paranoid => :very
|
123
|
+
}).import(options)
|
124
|
+
|
125
|
+
ssh_options[:port] = port
|
126
|
+
ssh_options[:keys] = private_key.nil? ? [] : [ private_key ]
|
127
|
+
|
128
|
+
session_id = session_id(hostname, user)
|
129
|
+
|
130
|
+
if reset || ! @@sessions.has_key?(session_id)
|
131
|
+
@@sessions[session_id] = Net::SSH.start(hostname, user, ssh_options.export)
|
132
|
+
end
|
133
|
+
yield(@@sessions[session_id]) if block_given? && @@sessions[session_id]
|
134
|
+
@@sessions[session_id]
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.init_session(hostname, user, port = 22, private_key = nil, options = {})
|
138
|
+
session(hostname, user, port, private_key, true, options)
|
139
|
+
end
|
140
|
+
|
141
|
+
#---
|
142
|
+
|
143
|
+
def self.close_session(hostname, user)
|
144
|
+
session_id = session_id(hostname, user)
|
145
|
+
|
146
|
+
if @@sessions.has_key?(session_id)
|
147
|
+
begin # Don't care about errors here
|
148
|
+
@@sessions[session_id].close
|
149
|
+
rescue
|
150
|
+
end
|
151
|
+
@@sessions.delete(session_id)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
#---
|
156
|
+
|
157
|
+
def self.close(hostname = nil, user = nil)
|
158
|
+
if hostname && user.nil? # Assume we entered a session id
|
159
|
+
if @@sessions.has_key?(hostname)
|
160
|
+
@@sessions[hostname].close
|
161
|
+
@@sessions.delete(hostname)
|
162
|
+
end
|
163
|
+
|
164
|
+
elsif hostname && user # Generate session id from args
|
165
|
+
session_id = session_id(hostname, user)
|
166
|
+
|
167
|
+
if @@sessions.has_key?(session_id)
|
168
|
+
@@sessions[session_id].close
|
169
|
+
@@sessions.delete(session_id)
|
170
|
+
end
|
171
|
+
|
172
|
+
else # Close all connections
|
173
|
+
@@sessions.keys.each do |id|
|
174
|
+
@@sessions[id].close
|
175
|
+
@@sessions.delete(id)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
#---
|
181
|
+
|
182
|
+
def self.exec(hostname, user, commands)
|
183
|
+
results = []
|
184
|
+
|
185
|
+
begin
|
186
|
+
session(hostname, user) do |ssh|
|
187
|
+
Data.array(commands).each do |command|
|
188
|
+
command = command.flatten.join(' ') if command.is_a?(Array)
|
189
|
+
command = command.to_s
|
190
|
+
result = Shell::Result.new(command)
|
191
|
+
|
192
|
+
ssh.open_channel do |ssh_channel|
|
193
|
+
ssh_channel.request_pty
|
194
|
+
ssh_channel.exec(command) do |channel, success|
|
195
|
+
unless success
|
196
|
+
raise "Could not execute command: #{command.inspect}"
|
197
|
+
end
|
198
|
+
|
199
|
+
channel.on_data do |ch, data|
|
200
|
+
result.append_output(data)
|
201
|
+
yield(:output, command, data) if block_given?
|
202
|
+
end
|
203
|
+
|
204
|
+
channel.on_extended_data do |ch, type, data|
|
205
|
+
next unless type == 1
|
206
|
+
result.append_errors(data)
|
207
|
+
yield(:error, command, data) if block_given?
|
208
|
+
end
|
209
|
+
|
210
|
+
channel.on_request('exit-status') do |ch, data|
|
211
|
+
result.status = data.read_long
|
212
|
+
end
|
213
|
+
|
214
|
+
channel.on_request('exit-signal') do |ch, data|
|
215
|
+
result.status = 255
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
ssh.loop
|
220
|
+
results << result
|
221
|
+
end
|
222
|
+
end
|
223
|
+
rescue Net::SSH::HostKeyMismatch => error
|
224
|
+
error.remember_host!
|
225
|
+
sleep 0.2
|
226
|
+
retry
|
227
|
+
end
|
228
|
+
results
|
229
|
+
end
|
230
|
+
|
231
|
+
#---
|
232
|
+
|
233
|
+
def self.download(hostname, user, remote_path, local_path, options = {})
|
234
|
+
config = Config.ensure(options)
|
235
|
+
|
236
|
+
require 'net/scp'
|
237
|
+
|
238
|
+
# Accepted options:
|
239
|
+
# * :recursive - the +remote+ parameter refers to a remote directory, which
|
240
|
+
# should be downloaded to a new directory named +local+ on the local
|
241
|
+
# machine.
|
242
|
+
# * :preserve - the atime and mtime of the file should be preserved.
|
243
|
+
# * :verbose - the process should result in verbose output on the server
|
244
|
+
# end (useful for debugging).
|
245
|
+
#
|
246
|
+
config.init(:recursive, true)
|
247
|
+
config.init(:preserve, true)
|
248
|
+
config.init(:verbose, true)
|
249
|
+
|
250
|
+
blocking = config.delete(:blocking, true)
|
251
|
+
|
252
|
+
session(hostname, user) do |ssh|
|
253
|
+
if blocking
|
254
|
+
ssh.scp.download!(remote_path, local_path, config.export) do |ch, name, received, total|
|
255
|
+
yield(name, received, total) if block_given?
|
256
|
+
end
|
257
|
+
else
|
258
|
+
ssh.scp.download(remote_path, local_path, config.export)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
#---
|
264
|
+
|
265
|
+
def self.upload(hostname, user, local_path, remote_path, options = {})
|
266
|
+
config = Config.ensure(options)
|
267
|
+
|
268
|
+
require 'net/scp'
|
269
|
+
|
270
|
+
# Accepted options:
|
271
|
+
# * :recursive - the +local+ parameter refers to a local directory, which
|
272
|
+
# should be uploaded to a new directory named +remote+ on the remote
|
273
|
+
# server.
|
274
|
+
# * :preserve - the atime and mtime of the file should be preserved.
|
275
|
+
# * :verbose - the process should result in verbose output on the server
|
276
|
+
# end (useful for debugging).
|
277
|
+
# * :chunk_size - the size of each "chunk" that should be sent. Defaults
|
278
|
+
# to 2048. Changing this value may improve throughput at the expense
|
279
|
+
# of decreasing interactivity.
|
280
|
+
#
|
281
|
+
config.init(:recursive, true)
|
282
|
+
config.init(:preserve, true)
|
283
|
+
config.init(:verbose, true)
|
284
|
+
config.init(:chunk_size, 2048)
|
285
|
+
|
286
|
+
blocking = config.delete(:blocking, true)
|
287
|
+
|
288
|
+
session(hostname, user) do |ssh|
|
289
|
+
if blocking
|
290
|
+
ssh.scp.upload!(local_path, remote_path, config.export) do |ch, name, sent, total|
|
291
|
+
yield(name, sent, total) if block_given?
|
292
|
+
end
|
293
|
+
else
|
294
|
+
ssh.scp.upload(local_path, remote_path, config.export)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
#---
|
300
|
+
|
301
|
+
#
|
302
|
+
# Inspired by vagrant ssh implementation
|
303
|
+
#
|
304
|
+
# See: https://github.com/mitchellh/vagrant/blob/master/lib/vagrant/util/ssh.rb
|
305
|
+
#
|
306
|
+
|
307
|
+
def self.terminal(hostname, user, options = {})
|
308
|
+
config = Config.ensure(options)
|
309
|
+
ssh_path = nucleon_locate("ssh")
|
310
|
+
|
311
|
+
raise Errors::SSHUnavailable unless ssh_path
|
312
|
+
|
313
|
+
port = config.get(:port, 22)
|
314
|
+
private_keys = config.get(:private_keys, File.join(ENV['HOME'], '.ssh', 'id_rsa'))
|
315
|
+
|
316
|
+
command_options = [
|
317
|
+
"#{user}@#{hostname}",
|
318
|
+
"-p", port.to_s,
|
319
|
+
"-o", "Compression=yes",
|
320
|
+
"-o", "DSAAuthentication=yes",
|
321
|
+
"-o", "LogLevel=FATAL",
|
322
|
+
"-o", "StrictHostKeyChecking=no",
|
323
|
+
"-o", "UserKnownHostsFile=/dev/null",
|
324
|
+
"-o", "IdentitiesOnly=yes"
|
325
|
+
]
|
326
|
+
|
327
|
+
Util::Data.array(private_keys).each do |path|
|
328
|
+
command_options += [ "-i", File.expand_path(path) ]
|
329
|
+
end
|
330
|
+
|
331
|
+
if config.get(:forward_x11, false)
|
332
|
+
command_options += [
|
333
|
+
"-o", "ForwardX11=yes",
|
334
|
+
"-o", "ForwardX11Trusted=yes"
|
335
|
+
]
|
336
|
+
end
|
337
|
+
|
338
|
+
command_options += [ "-o", "ProxyCommand=#{config[:proxy_command]}" ] if config.get(:proxy_command, false)
|
339
|
+
command_options += [ "-o", "ForwardAgent=yes" ] if config.get(:forward_agent, false)
|
340
|
+
|
341
|
+
command_options.concat(Util::Data.array(config[:extra_args])) if config.get(:extra_args, false)
|
342
|
+
|
343
|
+
#---
|
344
|
+
|
345
|
+
logger.info("Executing SSH in subprocess: #{command_options.inspect}")
|
346
|
+
|
347
|
+
process = ChildProcess.build('ssh', *command_options)
|
348
|
+
process.io.inherit!
|
349
|
+
|
350
|
+
process.start
|
351
|
+
process.wait
|
352
|
+
process.exit_code
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|