kdeploy 1.3.3 → 1.3.5
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/README.md +42 -1117
- data/README_EN.md +42 -1156
- data/lib/kdeploy/cli/cli.rb +28 -12
- data/lib/kdeploy/config/configuration.rb +24 -0
- data/lib/kdeploy/output/output_formatter.rb +3 -7
- data/lib/kdeploy/runner/runner.rb +2 -14
- data/lib/kdeploy/version.rb +1 -1
- metadata +2 -3
- data/AGENTS.md +0 -18
data/lib/kdeploy/cli/cli.rb
CHANGED
|
@@ -58,16 +58,16 @@ module Kdeploy
|
|
|
58
58
|
method_option :retry_policy, type: :string, desc: 'Retry policy JSON to override config file'
|
|
59
59
|
method_option :retry_policy_file, type: :string, desc: 'Retry policy JSON file to override config file'
|
|
60
60
|
def execute(task_file, task_name = nil)
|
|
61
|
-
load_config_file
|
|
62
|
-
show_banner_once
|
|
63
61
|
@task_file_dir = File.dirname(File.expand_path(task_file))
|
|
62
|
+
Configuration.load_for_execute(task_dir: @task_file_dir)
|
|
63
|
+
show_banner_once
|
|
64
64
|
load_task_file(task_file)
|
|
65
65
|
|
|
66
66
|
tasks_to_run = determine_tasks(task_name)
|
|
67
67
|
all_results = execute_tasks(tasks_to_run)
|
|
68
68
|
|
|
69
|
-
# Exit non-zero if any executed host failed.
|
|
70
|
-
exit 1 if any_failed?(all_results)
|
|
69
|
+
# Exit non-zero if any executed host failed or a task could not run.
|
|
70
|
+
exit 1 if any_failed?(all_results) || @task_errors
|
|
71
71
|
rescue StandardError => e
|
|
72
72
|
puts Kdeploy::Banner.show_error(e.message)
|
|
73
73
|
exit 1
|
|
@@ -76,7 +76,8 @@ module Kdeploy
|
|
|
76
76
|
private
|
|
77
77
|
|
|
78
78
|
def load_config_file
|
|
79
|
-
Configuration.
|
|
79
|
+
# ponytail: kept for compatibility; prefer Configuration.load_for_execute
|
|
80
|
+
Configuration.load_for_execute(task_dir: @task_file_dir)
|
|
80
81
|
end
|
|
81
82
|
|
|
82
83
|
def load_task_file(file)
|
|
@@ -107,6 +108,8 @@ module Kdeploy
|
|
|
107
108
|
return hosts unless limit
|
|
108
109
|
|
|
109
110
|
host_names = limit.split(',').map(&:strip)
|
|
111
|
+
unknown = host_names.reject { |name| hosts.key?(name) }
|
|
112
|
+
warn "Warning: --limit ignored unknown host(s): #{unknown.join(', ')}" if unknown.any?
|
|
110
113
|
hosts.slice(*host_names)
|
|
111
114
|
end
|
|
112
115
|
|
|
@@ -149,7 +152,7 @@ module Kdeploy
|
|
|
149
152
|
end
|
|
150
153
|
|
|
151
154
|
def print_host_result(host, result, formatter)
|
|
152
|
-
if %i[success
|
|
155
|
+
if %i[success].include?(result[:status])
|
|
153
156
|
print_success_result(host, result, formatter)
|
|
154
157
|
else
|
|
155
158
|
print_failure_result(host, result, formatter)
|
|
@@ -225,19 +228,31 @@ module Kdeploy
|
|
|
225
228
|
end
|
|
226
229
|
|
|
227
230
|
def determine_tasks(task_name)
|
|
228
|
-
|
|
231
|
+
if task_name
|
|
232
|
+
sym = task_name.to_sym
|
|
233
|
+
raise TaskNotFoundError, task_name unless self.class.kdeploy_tasks.key?(sym)
|
|
234
|
+
|
|
235
|
+
[sym]
|
|
236
|
+
else
|
|
237
|
+
self.class.kdeploy_tasks.keys
|
|
238
|
+
end
|
|
229
239
|
end
|
|
230
240
|
|
|
231
241
|
def execute_tasks(tasks_to_run)
|
|
232
242
|
all_results = {}
|
|
243
|
+
@task_errors = false
|
|
244
|
+
|
|
245
|
+
if tasks_to_run.empty?
|
|
246
|
+
puts Kdeploy::Banner.show_error('No tasks defined in task file')
|
|
247
|
+
@task_errors = true
|
|
248
|
+
return all_results
|
|
249
|
+
end
|
|
233
250
|
|
|
234
251
|
tasks_to_run.each do |task|
|
|
235
252
|
task_results = execute_single_task(task)
|
|
236
|
-
# Collect results for final summary
|
|
237
253
|
all_results[task] = task_results if task_results
|
|
238
254
|
|
|
239
|
-
|
|
240
|
-
break if task_failed?(task_results)
|
|
255
|
+
break if task_failed?(task_results) || @task_errors
|
|
241
256
|
end
|
|
242
257
|
|
|
243
258
|
# Show combined summary at the end for all tasks
|
|
@@ -252,6 +267,8 @@ module Kdeploy
|
|
|
252
267
|
|
|
253
268
|
if hosts.empty?
|
|
254
269
|
puts Kdeploy::Banner.show_error("No hosts found for task: #{task}")
|
|
270
|
+
puts 'This usually means no hosts matched the task configuration or --limit filter.'
|
|
271
|
+
@task_errors = true
|
|
255
272
|
return nil
|
|
256
273
|
end
|
|
257
274
|
|
|
@@ -323,10 +340,9 @@ module Kdeploy
|
|
|
323
340
|
all_hosts = {}
|
|
324
341
|
all_results.each do |task_name, task_results|
|
|
325
342
|
task_results.each do |host, result|
|
|
326
|
-
all_hosts[host] ||= { ok: 0,
|
|
343
|
+
all_hosts[host] ||= { ok: 0, failed: 0, tasks: [] }
|
|
327
344
|
counts = formatter.calculate_summary_counts(result)
|
|
328
345
|
all_hosts[host][:ok] += counts[:ok]
|
|
329
|
-
all_hosts[host][:changed] += counts[:changed]
|
|
330
346
|
all_hosts[host][:failed] += counts[:failed]
|
|
331
347
|
all_hosts[host][:tasks] << task_name
|
|
332
348
|
end
|
|
@@ -56,6 +56,22 @@ module Kdeploy
|
|
|
56
56
|
nil
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
# Load config for execute: CWD walk-up, then task file dir (overrides), then env vars.
|
|
60
|
+
def load_for_execute(task_dir: nil, cwd: Dir.pwd)
|
|
61
|
+
reset
|
|
62
|
+
load_from_file(find_config_file(cwd))
|
|
63
|
+
load_from_file(find_config_file(task_dir)) if task_dir
|
|
64
|
+
apply_env_overrides
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def apply_env_overrides
|
|
68
|
+
parallel = positive_int_env('KDEPLOY_PARALLEL')
|
|
69
|
+
@default_parallel = parallel if parallel
|
|
70
|
+
|
|
71
|
+
ssh_timeout = positive_int_env('KDEPLOY_SSH_TIMEOUT')
|
|
72
|
+
@default_ssh_timeout = ssh_timeout if ssh_timeout
|
|
73
|
+
end
|
|
74
|
+
|
|
59
75
|
def find_config_file(start_dir = Dir.pwd)
|
|
60
76
|
current_dir = File.expand_path(start_dir)
|
|
61
77
|
|
|
@@ -90,6 +106,14 @@ module Kdeploy
|
|
|
90
106
|
@default_sync_parallel = config['sync_parallel'] if config.key?('sync_parallel')
|
|
91
107
|
end
|
|
92
108
|
|
|
109
|
+
def positive_int_env(key)
|
|
110
|
+
raw = ENV.fetch(key, nil)
|
|
111
|
+
return nil if raw.nil? || raw.to_s.strip.empty?
|
|
112
|
+
|
|
113
|
+
value = raw.to_i
|
|
114
|
+
value.positive? ? value : nil
|
|
115
|
+
end
|
|
116
|
+
|
|
93
117
|
def parse_verify_host_key(value)
|
|
94
118
|
case value
|
|
95
119
|
when true, 'true', 'yes', 'always'
|
|
@@ -18,7 +18,6 @@ module Kdeploy
|
|
|
18
18
|
def format_host_status(host, status)
|
|
19
19
|
status_str = case status
|
|
20
20
|
when :success then @pastel.green('✓ ok')
|
|
21
|
-
when :changed then @pastel.yellow('~ changed')
|
|
22
21
|
else @pastel.red('✗ failed')
|
|
23
22
|
end
|
|
24
23
|
@pastel.bright_white(" #{host.ljust(20)} #{status_str}")
|
|
@@ -101,21 +100,18 @@ module Kdeploy
|
|
|
101
100
|
end
|
|
102
101
|
|
|
103
102
|
def calculate_summary_counts(result)
|
|
104
|
-
ok =
|
|
103
|
+
ok = result[:status] == :success ? result[:output].size : 0
|
|
105
104
|
failed = result[:status] == :failed ? 1 : 0
|
|
106
|
-
|
|
107
|
-
{ ok: ok, failed: failed, changed: changed }
|
|
105
|
+
{ ok: ok, failed: failed }
|
|
108
106
|
end
|
|
109
107
|
|
|
110
108
|
def build_summary_line(host, counts, max_host_len)
|
|
111
109
|
ok_w = 7
|
|
112
|
-
changed_w = 11
|
|
113
110
|
failed_w = 10
|
|
114
111
|
|
|
115
112
|
ok_str = @pastel.green("ok=#{counts[:ok].to_s.ljust(ok_w - 3)}")
|
|
116
|
-
changed_str = @pastel.yellow("changed=#{counts[:changed].to_s.ljust(changed_w - 8)}")
|
|
117
113
|
failed_str = @pastel.red("failed=#{counts[:failed].to_s.ljust(failed_w - 7)}")
|
|
118
|
-
"#{host.ljust(max_host_len)} : #{ok_str} #{
|
|
114
|
+
"#{host.ljust(max_host_len)} : #{ok_str} #{failed_str}"
|
|
119
115
|
end
|
|
120
116
|
|
|
121
117
|
def colorize_summary_line(line, counts)
|
|
@@ -11,8 +11,7 @@ module Kdeploy
|
|
|
11
11
|
retry_on_nonzero: Configuration.default_retry_on_nonzero,
|
|
12
12
|
host_timeout: Configuration.default_host_timeout,
|
|
13
13
|
step_timeout: Configuration.default_step_timeout,
|
|
14
|
-
retry_policy: Configuration.default_retry_policy
|
|
15
|
-
on_step: nil)
|
|
14
|
+
retry_policy: Configuration.default_retry_policy)
|
|
16
15
|
@hosts = hosts
|
|
17
16
|
@tasks = tasks
|
|
18
17
|
@parallel = parallel
|
|
@@ -25,7 +24,6 @@ module Kdeploy
|
|
|
25
24
|
@host_timeout = normalize_timeout(host_timeout)
|
|
26
25
|
@step_timeout = normalize_timeout(step_timeout)
|
|
27
26
|
@retry_policy = retry_policy
|
|
28
|
-
@on_step = on_step
|
|
29
27
|
@pool = Concurrent::FixedThreadPool.new(@parallel)
|
|
30
28
|
@results = Concurrent::Hash.new
|
|
31
29
|
end
|
|
@@ -67,6 +65,7 @@ module Kdeploy
|
|
|
67
65
|
pending.delete(future)
|
|
68
66
|
progressed = true
|
|
69
67
|
elsif timeout_exceeded?(started_at, now)
|
|
68
|
+
future.cancel if future.respond_to?(:cancel)
|
|
70
69
|
@results[host_name] ||= {
|
|
71
70
|
status: :failed,
|
|
72
71
|
error: "execution timeout after #{@host_timeout}s",
|
|
@@ -132,7 +131,6 @@ module Kdeploy
|
|
|
132
131
|
commands.each do |command|
|
|
133
132
|
step_result = execute_command(command_executor, command, name)
|
|
134
133
|
result[:output] << step_result
|
|
135
|
-
emit_step(name, step_result, result, task_name)
|
|
136
134
|
rescue StandardError => e
|
|
137
135
|
step = step_description(command)
|
|
138
136
|
result[:status] = :failed
|
|
@@ -144,7 +142,6 @@ module Kdeploy
|
|
|
144
142
|
error: "#{e.class}: #{e.message}",
|
|
145
143
|
output: error_output_for_step(e)
|
|
146
144
|
}
|
|
147
|
-
emit_step(name, result[:output].last, result, task_name)
|
|
148
145
|
break
|
|
149
146
|
end
|
|
150
147
|
end
|
|
@@ -218,15 +215,6 @@ module Kdeploy
|
|
|
218
215
|
end
|
|
219
216
|
end
|
|
220
217
|
|
|
221
|
-
def emit_step(host_name, step_result, result, task_name)
|
|
222
|
-
return unless @on_step
|
|
223
|
-
|
|
224
|
-
@on_step.call(host_name, step_result, result, task_name)
|
|
225
|
-
rescue StandardError
|
|
226
|
-
# Best-effort only; never fail execution because of streaming hooks.
|
|
227
|
-
nil
|
|
228
|
-
end
|
|
229
|
-
|
|
230
218
|
def execute_command(command_executor, command, host_name)
|
|
231
219
|
case command[:type]
|
|
232
220
|
when :run
|
data/lib/kdeploy/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kdeploy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kk
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bcrypt_pbkdf
|
|
@@ -160,7 +160,6 @@ extensions:
|
|
|
160
160
|
- ext/mkrf_conf.rb
|
|
161
161
|
extra_rdoc_files: []
|
|
162
162
|
files:
|
|
163
|
-
- AGENTS.md
|
|
164
163
|
- README.md
|
|
165
164
|
- README_EN.md
|
|
166
165
|
- exe/kdeploy
|
data/AGENTS.md
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
<!-- OPENSPEC:START -->
|
|
2
|
-
# OpenSpec Instructions
|
|
3
|
-
|
|
4
|
-
These instructions are for AI assistants working in this project.
|
|
5
|
-
|
|
6
|
-
Always open `@/openspec/AGENTS.md` when the request:
|
|
7
|
-
- Mentions planning or proposals (words like proposal, spec, change, plan)
|
|
8
|
-
- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work
|
|
9
|
-
- Sounds ambiguous and you need the authoritative spec before coding
|
|
10
|
-
|
|
11
|
-
Use `@/openspec/AGENTS.md` to learn:
|
|
12
|
-
- How to create and apply change proposals
|
|
13
|
-
- Spec format and conventions
|
|
14
|
-
- Project structure and guidelines
|
|
15
|
-
|
|
16
|
-
Keep this managed block so 'openspec update' can refresh the instructions.
|
|
17
|
-
|
|
18
|
-
<!-- OPENSPEC:END -->
|