kdeploy 1.2.1 → 1.2.2
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.
- checksums.yaml +4 -4
- data/OPTIMIZATION_COMPLETE.md +211 -0
- data/REFACTORING_SUMMARY.md +174 -0
- data/lib/kdeploy/cli.rb +134 -190
- data/lib/kdeploy/command_executor.rb +28 -11
- data/lib/kdeploy/dsl.rb +40 -21
- data/lib/kdeploy/executor.rb +48 -25
- data/lib/kdeploy/help_formatter.rb +67 -0
- data/lib/kdeploy/output.rb +2 -0
- data/lib/kdeploy/output_formatter.rb +194 -0
- data/lib/kdeploy/runner.rb +36 -17
- data/lib/kdeploy/template.rb +10 -2
- data/lib/kdeploy/version.rb +1 -1
- data/lib/kdeploy.rb +2 -0
- metadata +5 -1
data/lib/kdeploy/cli.rb
CHANGED
|
@@ -28,41 +28,7 @@ module Kdeploy
|
|
|
28
28
|
if command
|
|
29
29
|
super
|
|
30
30
|
else
|
|
31
|
-
|
|
32
|
-
puts Kdeploy::Banner.show
|
|
33
|
-
puts <<~HELP
|
|
34
|
-
#{pastel.bright_white('📖 Available Commands:')}
|
|
35
|
-
|
|
36
|
-
#{pastel.bright_yellow('🚀')} #{pastel.bright_white('execute TASK_FILE [TASK]')} Execute deployment tasks from file
|
|
37
|
-
#{pastel.dim(' --limit HOSTS')} Limit to specific hosts (comma-separated)
|
|
38
|
-
#{pastel.dim(' --parallel NUM')} Number of parallel executions (default: 5)
|
|
39
|
-
#{pastel.dim(' --dry-run')} Show what would be done without executing
|
|
40
|
-
|
|
41
|
-
#{pastel.bright_yellow('🆕')} #{pastel.bright_white('init [DIR]')} Initialize new deployment project
|
|
42
|
-
#{pastel.bright_yellow('ℹ️')} #{pastel.bright_white('version')} Show version information
|
|
43
|
-
#{pastel.bright_yellow('❓')} #{pastel.bright_white('help [COMMAND]')} Show help information
|
|
44
|
-
|
|
45
|
-
#{pastel.bright_white('💡 Examples:')}
|
|
46
|
-
|
|
47
|
-
#{pastel.dim('# Initialize a new project')}
|
|
48
|
-
#{pastel.bright_cyan('kdeploy init my-deployment')}
|
|
49
|
-
|
|
50
|
-
#{pastel.dim('# Deploy to web servers')}
|
|
51
|
-
#{pastel.bright_cyan('kdeploy execute deploy.rb deploy_web')}
|
|
52
|
-
|
|
53
|
-
#{pastel.dim('# Backup database')}
|
|
54
|
-
#{pastel.bright_cyan('kdeploy execute deploy.rb backup_db')}
|
|
55
|
-
|
|
56
|
-
#{pastel.dim('# Run maintenance on specific hosts')}
|
|
57
|
-
#{pastel.bright_cyan('kdeploy execute deploy.rb maintenance --limit web01')}
|
|
58
|
-
|
|
59
|
-
#{pastel.dim('# Preview deployment')}
|
|
60
|
-
#{pastel.bright_cyan('kdeploy execute deploy.rb deploy_web --dry-run')}
|
|
61
|
-
|
|
62
|
-
#{pastel.bright_white('📚 Documentation:')}
|
|
63
|
-
#{pastel.bright_cyan('https://github.com/kevin197011/kdeploy')}
|
|
64
|
-
|
|
65
|
-
HELP
|
|
31
|
+
show_general_help
|
|
66
32
|
end
|
|
67
33
|
end
|
|
68
34
|
|
|
@@ -80,40 +46,11 @@ module Kdeploy
|
|
|
80
46
|
method_option :parallel, type: :numeric, default: 10, desc: 'Number of parallel executions'
|
|
81
47
|
method_option :dry_run, type: :boolean, desc: 'Show what would be done'
|
|
82
48
|
def execute(task_file, task_name = nil)
|
|
83
|
-
|
|
84
|
-
@banner_printed ||= false
|
|
85
|
-
unless @banner_printed
|
|
86
|
-
puts Kdeploy::Banner.show
|
|
87
|
-
@banner_printed = true
|
|
88
|
-
end
|
|
89
|
-
|
|
49
|
+
show_banner_once
|
|
90
50
|
load_task_file(task_file)
|
|
91
51
|
|
|
92
|
-
tasks_to_run =
|
|
93
|
-
|
|
94
|
-
else
|
|
95
|
-
self.class.kdeploy_tasks.keys
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
tasks_to_run.each do |task|
|
|
99
|
-
task_hosts = self.class.get_task_hosts(task)
|
|
100
|
-
hosts = filter_hosts(options[:limit], task_hosts)
|
|
101
|
-
|
|
102
|
-
if hosts.empty?
|
|
103
|
-
puts Kdeploy::Banner.show_error("No hosts found for task: #{task}")
|
|
104
|
-
next
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
if options[:dry_run]
|
|
108
|
-
print_dry_run(hosts, task)
|
|
109
|
-
next
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
output = ConsoleOutput.new
|
|
113
|
-
runner = Runner.new(hosts, self.class.kdeploy_tasks, parallel: options[:parallel], output: output)
|
|
114
|
-
results = runner.run(task)
|
|
115
|
-
print_results(results, task)
|
|
116
|
-
end
|
|
52
|
+
tasks_to_run = determine_tasks(task_name)
|
|
53
|
+
execute_tasks(tasks_to_run)
|
|
117
54
|
rescue StandardError => e
|
|
118
55
|
puts Kdeploy::Banner.show_error(e.message)
|
|
119
56
|
exit 1
|
|
@@ -122,11 +59,7 @@ module Kdeploy
|
|
|
122
59
|
private
|
|
123
60
|
|
|
124
61
|
def load_task_file(file)
|
|
125
|
-
|
|
126
|
-
puts Kdeploy::Banner.show_error("Task file not found: #{file}")
|
|
127
|
-
exit 1
|
|
128
|
-
end
|
|
129
|
-
|
|
62
|
+
validate_task_file(file)
|
|
130
63
|
# 用 instance_eval 并传递顶层 binding,兼容 heredoc
|
|
131
64
|
self.class.module_eval(File.read(file), file)
|
|
132
65
|
rescue StandardError => e
|
|
@@ -135,6 +68,19 @@ module Kdeploy
|
|
|
135
68
|
raise
|
|
136
69
|
end
|
|
137
70
|
|
|
71
|
+
def validate_task_file(file)
|
|
72
|
+
return if File.exist?(file)
|
|
73
|
+
|
|
74
|
+
puts Kdeploy::Banner.show_error("Task file not found: #{file}")
|
|
75
|
+
exit 1
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def show_general_help
|
|
79
|
+
formatter = HelpFormatter.new
|
|
80
|
+
puts Kdeploy::Banner.show
|
|
81
|
+
puts formatter.format_help
|
|
82
|
+
end
|
|
83
|
+
|
|
138
84
|
def filter_hosts(limit, task_hosts)
|
|
139
85
|
hosts = self.class.kdeploy_hosts.slice(*task_hosts)
|
|
140
86
|
return hosts unless limit
|
|
@@ -144,139 +90,137 @@ module Kdeploy
|
|
|
144
90
|
end
|
|
145
91
|
|
|
146
92
|
def print_dry_run(hosts, task_name)
|
|
93
|
+
formatter = OutputFormatter.new
|
|
147
94
|
puts Kdeploy::Banner.show
|
|
148
|
-
|
|
149
|
-
puts TTY::Box.frame(
|
|
150
|
-
'Showing what would be done without executing any commands',
|
|
151
|
-
title: { top_left: ' Dry Run Mode ', bottom_right: ' Kdeploy ' },
|
|
152
|
-
style: {
|
|
153
|
-
border: {
|
|
154
|
-
fg: :blue
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
)
|
|
95
|
+
puts formatter.format_dry_run_header
|
|
158
96
|
puts
|
|
159
97
|
|
|
160
98
|
hosts.each do |name, config|
|
|
161
|
-
|
|
162
|
-
output = commands.map do |command|
|
|
163
|
-
case command[:type]
|
|
164
|
-
when :run
|
|
165
|
-
"#{pastel.green('>')} #{command[:command]}"
|
|
166
|
-
when :upload
|
|
167
|
-
"#{pastel.blue('>')} Upload: #{command[:source]} -> #{command[:destination]}"
|
|
168
|
-
end
|
|
169
|
-
end.join("\n")
|
|
170
|
-
|
|
171
|
-
puts TTY::Box.frame(
|
|
172
|
-
output,
|
|
173
|
-
title: { top_left: " #{name} (#{config[:ip]}) " },
|
|
174
|
-
style: {
|
|
175
|
-
border: {
|
|
176
|
-
fg: :yellow
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
)
|
|
180
|
-
puts
|
|
99
|
+
print_dry_run_for_host(name, config, task_name, formatter)
|
|
181
100
|
end
|
|
182
101
|
end
|
|
183
102
|
|
|
103
|
+
def print_dry_run_for_host(name, config, task_name, formatter)
|
|
104
|
+
commands = self.class.kdeploy_tasks[task_name][:block].call
|
|
105
|
+
output = commands.map { |cmd| formatter.format_command_for_dry_run(cmd) }.join("\n")
|
|
106
|
+
title = "#{name} (#{config[:ip]})"
|
|
107
|
+
puts formatter.format_dry_run_box(title, output)
|
|
108
|
+
puts
|
|
109
|
+
end
|
|
110
|
+
|
|
184
111
|
def print_results(results, task_name)
|
|
185
|
-
|
|
186
|
-
puts
|
|
112
|
+
formatter = OutputFormatter.new
|
|
113
|
+
puts formatter.format_task_header(task_name)
|
|
187
114
|
|
|
188
115
|
results.each do |host, result|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
when :upload_template
|
|
212
|
-
puts pastel.yellow(' === Template ===')
|
|
213
|
-
steps.each do |step|
|
|
214
|
-
key = [step[:command], type].hash
|
|
215
|
-
next if shown[key]
|
|
216
|
-
|
|
217
|
-
shown[key] = true
|
|
218
|
-
duration_str = step[:duration] ? pastel.dim(" [#{format('%.2f', step[:duration])}s]") : ''
|
|
219
|
-
puts pastel.yellow(" [template] #{step[:command].sub('upload_template: ', '')}#{duration_str}")
|
|
220
|
-
end
|
|
221
|
-
when :run
|
|
222
|
-
puts pastel.cyan(' === Run ===')
|
|
223
|
-
steps.each do |step|
|
|
224
|
-
key = [step[:command], type].hash
|
|
225
|
-
next if shown[key]
|
|
226
|
-
|
|
227
|
-
shown[key] = true
|
|
228
|
-
duration_str = step[:duration] ? pastel.dim(" [#{format('%.2f', step[:duration])}s]") : ''
|
|
229
|
-
puts pastel.cyan(" [run] #{step[:command].to_s.lines.first.strip}#{duration_str}")
|
|
230
|
-
# 多行命令内容高亮
|
|
231
|
-
cmd_lines = step[:command].to_s.lines[1..].map(&:strip).reject(&:empty?)
|
|
232
|
-
cmd_lines.each { |line| puts pastel.cyan(" > #{line}") } if cmd_lines.any?
|
|
233
|
-
if step[:output].is_a?(Hash) && step[:output][:stdout]
|
|
234
|
-
step[:output][:stdout].each_line do |line|
|
|
235
|
-
puts pastel.green(" #{line.rstrip}") unless line.strip.empty?
|
|
236
|
-
end
|
|
237
|
-
elsif step[:output].is_a?(String)
|
|
238
|
-
step[:output].each_line do |line|
|
|
239
|
-
puts pastel.green(" #{line.rstrip}") unless line.strip.empty?
|
|
240
|
-
end
|
|
241
|
-
end
|
|
242
|
-
end
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
else
|
|
246
|
-
# 失败主机高亮错误
|
|
247
|
-
err = result[:error] || (if result[:output].is_a?(Array)
|
|
248
|
-
result[:output].map do |o|
|
|
249
|
-
o[:output][:stderr] if o[:output].is_a?(Hash)
|
|
250
|
-
end.compact.join("\n")
|
|
251
|
-
else
|
|
252
|
-
result[:output].to_s
|
|
253
|
-
end)
|
|
254
|
-
puts pastel.red(" ERROR: #{err}")
|
|
255
|
-
end
|
|
116
|
+
puts formatter.format_host_status(host, result[:status])
|
|
117
|
+
print_host_result(host, result, formatter)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
print_summary(results, formatter)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def print_host_result(_host, result, formatter)
|
|
124
|
+
if %i[success changed].include?(result[:status])
|
|
125
|
+
print_success_result(result, formatter)
|
|
126
|
+
else
|
|
127
|
+
print_failure_result(result, formatter)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def print_success_result(result, formatter)
|
|
132
|
+
shown = {}
|
|
133
|
+
grouped = group_output_by_type(result[:output])
|
|
134
|
+
|
|
135
|
+
grouped.each do |type, steps|
|
|
136
|
+
output_lines = format_steps_by_type(type, steps, shown, formatter)
|
|
137
|
+
output_lines.each { |line| puts line }
|
|
256
138
|
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def group_output_by_type(output)
|
|
142
|
+
output.group_by { |step| step[:type] || :run }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def format_steps_by_type(type, steps, shown, formatter)
|
|
146
|
+
case type
|
|
147
|
+
when :upload
|
|
148
|
+
formatter.format_upload_steps(steps, shown)
|
|
149
|
+
when :upload_template
|
|
150
|
+
formatter.format_template_steps(steps, shown)
|
|
151
|
+
when :run
|
|
152
|
+
formatter.format_run_steps(steps, shown)
|
|
153
|
+
else
|
|
154
|
+
[]
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def print_failure_result(result, formatter)
|
|
159
|
+
error_message = extract_error_message(result)
|
|
160
|
+
puts formatter.format_error(error_message)
|
|
161
|
+
end
|
|
257
162
|
|
|
258
|
-
|
|
259
|
-
puts
|
|
163
|
+
def print_summary(results, formatter)
|
|
164
|
+
puts formatter.format_summary_header
|
|
260
165
|
max_host_len = results.keys.map(&:length).max || 16
|
|
261
|
-
|
|
262
|
-
changed_w = 11
|
|
263
|
-
failed_w = 10
|
|
166
|
+
|
|
264
167
|
results.keys.sort.each do |host|
|
|
265
168
|
result = results[host]
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
169
|
+
puts formatter.format_summary_line(host, result, max_host_len)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def show_banner_once
|
|
174
|
+
@banner_printed ||= false
|
|
175
|
+
return if @banner_printed
|
|
176
|
+
|
|
177
|
+
puts Kdeploy::Banner.show
|
|
178
|
+
@banner_printed = true
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def determine_tasks(task_name)
|
|
182
|
+
task_name ? [task_name.to_sym] : self.class.kdeploy_tasks.keys
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def execute_tasks(tasks_to_run)
|
|
186
|
+
tasks_to_run.each do |task|
|
|
187
|
+
execute_single_task(task)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def execute_single_task(task)
|
|
192
|
+
task_hosts = self.class.get_task_hosts(task)
|
|
193
|
+
hosts = filter_hosts(options[:limit], task_hosts)
|
|
194
|
+
|
|
195
|
+
if hosts.empty?
|
|
196
|
+
puts Kdeploy::Banner.show_error("No hosts found for task: #{task}")
|
|
197
|
+
return
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
if options[:dry_run]
|
|
201
|
+
print_dry_run(hosts, task)
|
|
202
|
+
return
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
run_task(hosts, task)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def run_task(hosts, task)
|
|
209
|
+
output = ConsoleOutput.new
|
|
210
|
+
runner = Runner.new(hosts, self.class.kdeploy_tasks, parallel: options[:parallel], output: output)
|
|
211
|
+
results = runner.run(task)
|
|
212
|
+
print_results(results, task)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def extract_error_message(result)
|
|
216
|
+
return result[:error] if result[:error]
|
|
217
|
+
|
|
218
|
+
if result[:output].is_a?(Array)
|
|
219
|
+
result[:output].map do |o|
|
|
220
|
+
o[:output][:stderr] if o[:output].is_a?(Hash)
|
|
221
|
+
end.compact.join("\n")
|
|
222
|
+
else
|
|
223
|
+
result[:output].to_s
|
|
280
224
|
end
|
|
281
225
|
end
|
|
282
226
|
end
|
|
@@ -54,35 +54,52 @@ module Kdeploy
|
|
|
54
54
|
return unless output.is_a?(Hash)
|
|
55
55
|
|
|
56
56
|
pastel = @output.respond_to?(:pastel) ? @output.pastel : Pastel.new
|
|
57
|
+
show_stdout(output[:stdout])
|
|
58
|
+
show_stderr(output[:stderr], pastel)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def show_stdout(stdout)
|
|
62
|
+
return unless stdout && !stdout.empty?
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
output
|
|
60
|
-
@output.write_line(" #{line.rstrip}") unless line.strip.empty?
|
|
61
|
-
end
|
|
64
|
+
stdout.each_line do |line|
|
|
65
|
+
@output.write_line(" #{line.rstrip}") unless line.strip.empty?
|
|
62
66
|
end
|
|
67
|
+
end
|
|
63
68
|
|
|
64
|
-
|
|
69
|
+
def show_stderr(stderr, pastel)
|
|
70
|
+
return unless stderr && !stderr.empty?
|
|
65
71
|
|
|
66
|
-
|
|
72
|
+
stderr.each_line do |line|
|
|
67
73
|
@output.write_line(pastel.green(" #{line.rstrip}")) unless line.strip.empty?
|
|
68
74
|
end
|
|
69
75
|
end
|
|
70
76
|
|
|
71
77
|
def show_command_header(host_name, type, description)
|
|
72
|
-
pastel =
|
|
78
|
+
pastel = get_pastel
|
|
73
79
|
@output.write_line(pastel.bright_white("\n#{host_name.ljust(24)} : "))
|
|
80
|
+
format_command_by_type(type, description, pastel)
|
|
81
|
+
end
|
|
74
82
|
|
|
83
|
+
def get_pastel
|
|
84
|
+
@output.respond_to?(:pastel) ? @output.pastel : Pastel.new
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def format_command_by_type(type, description, pastel)
|
|
75
88
|
case type
|
|
76
89
|
when :run
|
|
77
|
-
|
|
78
|
-
description.lines[1..].each do |line|
|
|
79
|
-
@output.write_line(" > #{line.strip}") unless line.strip.empty?
|
|
80
|
-
end
|
|
90
|
+
format_run_command(description, pastel)
|
|
81
91
|
when :upload
|
|
82
92
|
@output.write_line(pastel.green(" [upload] #{description}"))
|
|
83
93
|
when :upload_template
|
|
84
94
|
@output.write_line(pastel.yellow(" [template] #{description}"))
|
|
85
95
|
end
|
|
86
96
|
end
|
|
97
|
+
|
|
98
|
+
def format_run_command(description, pastel)
|
|
99
|
+
@output.write_line(pastel.cyan(" [run] #{description.lines.first.strip}"))
|
|
100
|
+
description.lines[1..].each do |line|
|
|
101
|
+
@output.write_line(" > #{line.strip}") unless line.strip.empty?
|
|
102
|
+
end
|
|
103
|
+
end
|
|
87
104
|
end
|
|
88
105
|
end
|
data/lib/kdeploy/dsl.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'set'
|
|
4
4
|
|
|
5
5
|
module Kdeploy
|
|
6
|
+
# Domain-specific language for defining hosts, roles, and tasks
|
|
6
7
|
module DSL
|
|
7
8
|
def self.extended(base)
|
|
8
9
|
base.instance_variable_set(:@kdeploy_hosts, {})
|
|
@@ -32,21 +33,31 @@ module Kdeploy
|
|
|
32
33
|
|
|
33
34
|
def task(name, on: nil, roles: nil, &block)
|
|
34
35
|
kdeploy_tasks[name] = {
|
|
35
|
-
hosts:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
36
|
+
hosts: normalize_hosts_option(on),
|
|
37
|
+
roles: normalize_roles_option(roles),
|
|
38
|
+
block: create_task_block(block)
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def normalize_hosts_option(on)
|
|
43
|
+
return on if on.is_a?(Array)
|
|
44
|
+
return [on] if on
|
|
45
|
+
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def normalize_roles_option(roles)
|
|
50
|
+
return roles if roles.is_a?(Array)
|
|
51
|
+
return [roles] if roles
|
|
52
|
+
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def create_task_block(block)
|
|
57
|
+
lambda {
|
|
58
|
+
@kdeploy_commands = []
|
|
59
|
+
instance_eval(&block)
|
|
60
|
+
@kdeploy_commands
|
|
50
61
|
}
|
|
51
62
|
end
|
|
52
63
|
|
|
@@ -76,25 +87,33 @@ module Kdeploy
|
|
|
76
87
|
|
|
77
88
|
def get_task_hosts(task_name)
|
|
78
89
|
task = kdeploy_tasks[task_name]
|
|
79
|
-
return kdeploy_hosts.keys if
|
|
90
|
+
return kdeploy_hosts.keys if task_empty?(task)
|
|
80
91
|
|
|
81
92
|
hosts = Set.new
|
|
93
|
+
add_explicit_hosts(task, hosts)
|
|
94
|
+
add_role_hosts(task, hosts)
|
|
95
|
+
hosts.to_a
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def task_empty?(task)
|
|
99
|
+
!task || (!task[:hosts] && !task[:roles])
|
|
100
|
+
end
|
|
82
101
|
|
|
83
|
-
|
|
102
|
+
def add_explicit_hosts(task, hosts)
|
|
84
103
|
task[:hosts]&.each do |host|
|
|
85
104
|
hosts.add(host) if kdeploy_hosts.key?(host)
|
|
86
105
|
end
|
|
106
|
+
end
|
|
87
107
|
|
|
88
|
-
|
|
108
|
+
def add_role_hosts(task, hosts)
|
|
89
109
|
task[:roles]&.each do |role|
|
|
90
|
-
|
|
110
|
+
role_hosts = kdeploy_roles[role]
|
|
111
|
+
next unless role_hosts
|
|
91
112
|
|
|
92
113
|
role_hosts.each do |host|
|
|
93
114
|
hosts.add(host) if kdeploy_hosts.key?(host)
|
|
94
115
|
end
|
|
95
116
|
end
|
|
96
|
-
|
|
97
|
-
hosts.to_a
|
|
98
117
|
end
|
|
99
118
|
end
|
|
100
119
|
end
|
data/lib/kdeploy/executor.rb
CHANGED
|
@@ -17,28 +17,7 @@ module Kdeploy
|
|
|
17
17
|
|
|
18
18
|
def execute(command)
|
|
19
19
|
Net::SSH.start(@ip, @user, ssh_options) do |ssh|
|
|
20
|
-
|
|
21
|
-
stderr = String.new
|
|
22
|
-
|
|
23
|
-
ssh.open_channel do |channel|
|
|
24
|
-
channel.exec(command) do |_ch, success|
|
|
25
|
-
raise SSHError, "Could not execute command: #{command}" unless success
|
|
26
|
-
|
|
27
|
-
channel.on_data do |_ch, data|
|
|
28
|
-
stdout << data
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
channel.on_extended_data do |_ch, _type, data|
|
|
32
|
-
stderr << data
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
ssh.loop
|
|
37
|
-
{
|
|
38
|
-
stdout: stdout.strip,
|
|
39
|
-
stderr: stderr.strip,
|
|
40
|
-
command: command
|
|
41
|
-
}
|
|
20
|
+
execute_command_on_ssh(ssh, command)
|
|
42
21
|
end
|
|
43
22
|
rescue Net::SSH::AuthenticationFailed => e
|
|
44
23
|
raise SSHError.new("SSH authentication failed: #{e.message}", e)
|
|
@@ -46,6 +25,39 @@ module Kdeploy
|
|
|
46
25
|
raise SSHError.new("SSH execution failed: #{e.message}", e)
|
|
47
26
|
end
|
|
48
27
|
|
|
28
|
+
def execute_command_on_ssh(ssh, command)
|
|
29
|
+
stdout = String.new
|
|
30
|
+
stderr = String.new
|
|
31
|
+
|
|
32
|
+
ssh.open_channel do |channel|
|
|
33
|
+
channel.exec(command) do |_ch, success|
|
|
34
|
+
raise SSHError, "Could not execute command: #{command}" unless success
|
|
35
|
+
|
|
36
|
+
setup_channel_handlers(channel, stdout, stderr)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
ssh.loop
|
|
40
|
+
build_command_result(stdout, stderr, command)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def setup_channel_handlers(channel, stdout, stderr)
|
|
44
|
+
channel.on_data do |_ch, data|
|
|
45
|
+
stdout << data
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
channel.on_extended_data do |_ch, _type, data|
|
|
49
|
+
stderr << data
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def build_command_result(stdout, stderr, command)
|
|
54
|
+
{
|
|
55
|
+
stdout: stdout.strip,
|
|
56
|
+
stderr: stderr.strip,
|
|
57
|
+
command: command
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
49
61
|
def upload(source, destination)
|
|
50
62
|
Net::SCP.start(@ip, @user, ssh_options) do |scp|
|
|
51
63
|
scp.upload!(source, destination)
|
|
@@ -63,18 +75,29 @@ module Kdeploy
|
|
|
63
75
|
private
|
|
64
76
|
|
|
65
77
|
def ssh_options
|
|
66
|
-
options =
|
|
78
|
+
options = base_ssh_options
|
|
79
|
+
add_authentication(options)
|
|
80
|
+
add_port_option(options)
|
|
81
|
+
options
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def base_ssh_options
|
|
85
|
+
{
|
|
67
86
|
verify_host_key: :never,
|
|
68
87
|
timeout: 30
|
|
69
88
|
}
|
|
89
|
+
end
|
|
70
90
|
|
|
91
|
+
def add_authentication(options)
|
|
71
92
|
if @password
|
|
72
93
|
options[:password] = @password
|
|
73
94
|
elsif @key
|
|
74
95
|
options[:keys] = [@key]
|
|
75
96
|
end
|
|
76
|
-
|
|
77
|
-
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def add_port_option(options)
|
|
100
|
+
options[:port] = @port if @port
|
|
78
101
|
end
|
|
79
102
|
end
|
|
80
103
|
end
|