kdeploy 1.3.3 → 1.3.4

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.
@@ -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.load_from_file
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 changed].include?(result[:status])
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
- task_name ? [task_name.to_sym] : self.class.kdeploy_tasks.keys
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
- # Stop executing remaining tasks once any host failed for this task.
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, changed: 0, failed: 0, tasks: [] }
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 = %i[success changed].include?(result[:status]) ? result[:output].size : 0
103
+ ok = result[:status] == :success ? result[:output].size : 0
105
104
  failed = result[:status] == :failed ? 1 : 0
106
- changed = result[:status] == :changed ? result[:output].size : 0
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} #{changed_str} #{failed_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
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Kdeploy module for version management
4
4
  module Kdeploy
5
- VERSION = '1.3.3' unless const_defined?(:VERSION)
5
+ VERSION = '1.3.4' unless const_defined?(:VERSION)
6
6
  end
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.3
4
+ version: 1.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kk
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-15 00:00:00.000000000 Z
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 -->