kdeploy 1.2.41 → 1.3.1
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 +8 -1
- data/README_EN.md +8 -0
- data/lib/kdeploy/cli.rb +10 -1
- data/lib/kdeploy/command_executor.rb +4 -2
- data/lib/kdeploy/configuration.rb +9 -1
- data/lib/kdeploy/help_formatter.rb +2 -0
- data/lib/kdeploy/runner.rb +95 -43
- data/lib/kdeploy/version.rb +1 -1
- metadata +1 -2
- data/r.md +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 782163100dc54c6588d66c1655434a9ab4f6a0cd3aa5d97583e4b9e5fb757bbd
|
|
4
|
+
data.tar.gz: 38c8a9e92920dcdbfdca974a9ed4a51579cb618cc2a80fedcdaf19f0fe73fd80
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bcf659df1c65bfc50c0ebdb57a7b9f689a74c9db7107f7d3c48beb869b37633b2b887f02ca27f8f0b1707c5a593b8727e0416754221654c31faeaa1c1ddc2a3c
|
|
7
|
+
data.tar.gz: 2fa594ddb0cf3196dc1abcfd9ae9fb54729b91907445912a0e10cd4aa5d7da83c42ac9dbab02ee33e8d824b38ef25408f35e7791108e9b26728a1b96507bd816
|
data/README.md
CHANGED
|
@@ -207,6 +207,8 @@ kdeploy execute deploy.rb deploy_web
|
|
|
207
207
|
- `--format FORMAT`: 输出格式(`text`|`json`,默认 `text`)
|
|
208
208
|
- `--retries N`: 网络相关操作重试次数(默认 `0`)
|
|
209
209
|
- `--retry-delay SECONDS`: 每次重试间隔秒数(默认 `1`)
|
|
210
|
+
- `--retry-on-nonzero`: 非零退出码重试开关(默认 `false`)
|
|
211
|
+
- `--timeout SECONDS`: 单 host 执行超时(秒,默认不启用)
|
|
210
212
|
|
|
211
213
|
**示例:**
|
|
212
214
|
```bash
|
|
@@ -228,6 +230,12 @@ kdeploy execute deploy.rb deploy_web --format json --no-banner
|
|
|
228
230
|
# 重试网络抖动导致的失败
|
|
229
231
|
kdeploy execute deploy.rb deploy_web --retries 3 --retry-delay 1
|
|
230
232
|
|
|
233
|
+
# 对非零退出码进行重试
|
|
234
|
+
kdeploy execute deploy.rb deploy_web --retries 2 --retry-on-nonzero
|
|
235
|
+
|
|
236
|
+
# 设置单 host 超时(秒)
|
|
237
|
+
kdeploy execute deploy.rb deploy_web --timeout 120
|
|
238
|
+
|
|
231
239
|
# 组合选项
|
|
232
240
|
kdeploy execute deploy.rb deploy_web --limit web01 --parallel 3 --dry-run
|
|
233
241
|
```
|
|
@@ -1141,4 +1149,3 @@ end
|
|
|
1141
1149
|
---
|
|
1142
1150
|
|
|
1143
1151
|
**为 DevOps 社区用 ❤️ 制作**
|
|
1144
|
-
|
data/README_EN.md
CHANGED
|
@@ -206,6 +206,8 @@ kdeploy execute deploy.rb deploy_web
|
|
|
206
206
|
- `--format FORMAT`: Output format (`text`|`json`, default `text`)
|
|
207
207
|
- `--retries N`: Retry count for network operations (default `0`)
|
|
208
208
|
- `--retry-delay SECONDS`: Delay between retries in seconds (default `1`)
|
|
209
|
+
- `--retry-on-nonzero`: Retry commands on nonzero exit status (default `false`)
|
|
210
|
+
- `--timeout SECONDS`: Per-host execution timeout in seconds (default: none)
|
|
209
211
|
|
|
210
212
|
**Examples:**
|
|
211
213
|
```bash
|
|
@@ -227,6 +229,12 @@ kdeploy execute deploy.rb deploy_web --format json --no-banner
|
|
|
227
229
|
# Retry transient network failures
|
|
228
230
|
kdeploy execute deploy.rb deploy_web --retries 3 --retry-delay 1
|
|
229
231
|
|
|
232
|
+
# Retry on nonzero exit status
|
|
233
|
+
kdeploy execute deploy.rb deploy_web --retries 2 --retry-on-nonzero
|
|
234
|
+
|
|
235
|
+
# Set per-host timeout (seconds)
|
|
236
|
+
kdeploy execute deploy.rb deploy_web --timeout 120
|
|
237
|
+
|
|
230
238
|
# Combine options
|
|
231
239
|
kdeploy execute deploy.rb deploy_web --limit web01 --parallel 3 --dry-run
|
|
232
240
|
```
|
data/lib/kdeploy/cli.rb
CHANGED
|
@@ -51,6 +51,8 @@ module Kdeploy
|
|
|
51
51
|
method_option :format, type: :string, default: 'text', desc: 'Output format (text|json)'
|
|
52
52
|
method_option :retries, type: :numeric, desc: 'Retry count for network operations (default: 0)'
|
|
53
53
|
method_option :retry_delay, type: :numeric, desc: 'Retry delay seconds (default: 1)'
|
|
54
|
+
method_option :retry_on_nonzero, type: :boolean, desc: 'Retry commands on nonzero exit status (default: false)'
|
|
55
|
+
method_option :timeout, type: :numeric, desc: 'Per-host execution timeout seconds (default: none)'
|
|
54
56
|
def execute(task_file, task_name = nil)
|
|
55
57
|
load_config_file
|
|
56
58
|
show_banner_once
|
|
@@ -267,6 +269,9 @@ module Kdeploy
|
|
|
267
269
|
debug_mode = options[:debug] || false
|
|
268
270
|
retries = options[:retries].nil? ? Configuration.default_retries : options[:retries]
|
|
269
271
|
retry_delay = options[:retry_delay].nil? ? Configuration.default_retry_delay : options[:retry_delay]
|
|
272
|
+
retry_on_nonzero =
|
|
273
|
+
options[:retry_on_nonzero].nil? ? Configuration.default_retry_on_nonzero : options[:retry_on_nonzero]
|
|
274
|
+
host_timeout = options[:timeout].nil? ? Configuration.default_host_timeout : options[:timeout]
|
|
270
275
|
base_dir = @task_file_dir
|
|
271
276
|
runner = Runner.new(
|
|
272
277
|
hosts, self.class.kdeploy_tasks,
|
|
@@ -275,7 +280,9 @@ module Kdeploy
|
|
|
275
280
|
debug: debug_mode,
|
|
276
281
|
base_dir: base_dir,
|
|
277
282
|
retries: retries,
|
|
278
|
-
retry_delay: retry_delay
|
|
283
|
+
retry_delay: retry_delay,
|
|
284
|
+
retry_on_nonzero: retry_on_nonzero,
|
|
285
|
+
host_timeout: host_timeout
|
|
279
286
|
)
|
|
280
287
|
results = runner.run(task)
|
|
281
288
|
if options[:format] == 'json'
|
|
@@ -363,6 +370,8 @@ module Kdeploy
|
|
|
363
370
|
duration: step[:duration]
|
|
364
371
|
}
|
|
365
372
|
|
|
373
|
+
out[:exit_status] = step[:output][:exit_status] if step[:output].is_a?(Hash) && step[:output].key?(:exit_status)
|
|
374
|
+
|
|
366
375
|
out[:result] = step[:result] if step[:type] == :sync
|
|
367
376
|
|
|
368
377
|
if options[:debug] && step[:type] == :run && step[:output].is_a?(Hash)
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
module Kdeploy
|
|
4
4
|
# Executes a single command and records execution time
|
|
5
5
|
class CommandExecutor
|
|
6
|
-
def initialize(executor, output, debug: false, retries: 0, retry_delay: 1)
|
|
6
|
+
def initialize(executor, output, debug: false, retries: 0, retry_delay: 1, retry_on_nonzero: false)
|
|
7
7
|
@executor = executor
|
|
8
8
|
@output = output
|
|
9
9
|
@debug = debug
|
|
10
10
|
@retries = retries.to_i
|
|
11
11
|
@retry_delay = retry_delay.to_f
|
|
12
|
+
@retry_on_nonzero = retry_on_nonzero
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def execute_run(command, _host_name)
|
|
@@ -91,7 +92,8 @@ module Kdeploy
|
|
|
91
92
|
begin
|
|
92
93
|
attempts += 1
|
|
93
94
|
yield
|
|
94
|
-
rescue SSHError, SCPError, TemplateError
|
|
95
|
+
rescue SSHError, SCPError, TemplateError => e
|
|
96
|
+
raise if e.is_a?(SSHError) && e.exit_status && !@retry_on_nonzero
|
|
95
97
|
raise if attempts > (@retries + 1)
|
|
96
98
|
|
|
97
99
|
sleep(@retry_delay) if @retry_delay.positive?
|
|
@@ -10,6 +10,8 @@ module Kdeploy
|
|
|
10
10
|
DEFAULT_VERIFY_HOST_KEY = :never
|
|
11
11
|
DEFAULT_RETRIES = 0
|
|
12
12
|
DEFAULT_RETRY_DELAY = 1
|
|
13
|
+
DEFAULT_HOST_TIMEOUT = nil
|
|
14
|
+
DEFAULT_RETRY_ON_NONZERO = false
|
|
13
15
|
CONFIG_FILE_NAME = '.kdeploy.yml'
|
|
14
16
|
|
|
15
17
|
class << self
|
|
@@ -17,7 +19,9 @@ module Kdeploy
|
|
|
17
19
|
:default_ssh_timeout,
|
|
18
20
|
:default_verify_host_key,
|
|
19
21
|
:default_retries,
|
|
20
|
-
:default_retry_delay
|
|
22
|
+
:default_retry_delay,
|
|
23
|
+
:default_host_timeout,
|
|
24
|
+
:default_retry_on_nonzero
|
|
21
25
|
|
|
22
26
|
def reset
|
|
23
27
|
@default_parallel = DEFAULT_PARALLEL
|
|
@@ -25,6 +29,8 @@ module Kdeploy
|
|
|
25
29
|
@default_verify_host_key = DEFAULT_VERIFY_HOST_KEY
|
|
26
30
|
@default_retries = DEFAULT_RETRIES
|
|
27
31
|
@default_retry_delay = DEFAULT_RETRY_DELAY
|
|
32
|
+
@default_host_timeout = DEFAULT_HOST_TIMEOUT
|
|
33
|
+
@default_retry_on_nonzero = DEFAULT_RETRY_ON_NONZERO
|
|
28
34
|
end
|
|
29
35
|
|
|
30
36
|
def load_from_file(config_path = nil)
|
|
@@ -64,6 +70,8 @@ module Kdeploy
|
|
|
64
70
|
@default_verify_host_key = parse_verify_host_key(config['verify_host_key']) if config.key?('verify_host_key')
|
|
65
71
|
@default_retries = config['retries'] if config.key?('retries')
|
|
66
72
|
@default_retry_delay = config['retry_delay'] if config.key?('retry_delay')
|
|
73
|
+
@default_host_timeout = config['host_timeout'] if config.key?('host_timeout')
|
|
74
|
+
@default_retry_on_nonzero = config['retry_on_nonzero'] if config.key?('retry_on_nonzero')
|
|
67
75
|
end
|
|
68
76
|
|
|
69
77
|
def parse_verify_host_key(value)
|
|
@@ -34,6 +34,8 @@ module Kdeploy
|
|
|
34
34
|
#{@pastel.dim(' --format FORMAT')} Output format (text|json)
|
|
35
35
|
#{@pastel.dim(' --retries N')} Retry count for network operations (default: 0; overridden by .kdeploy.yml)
|
|
36
36
|
#{@pastel.dim(' --retry-delay SECONDS')} Retry delay seconds (default: 1; overridden by .kdeploy.yml)
|
|
37
|
+
#{@pastel.dim(' --retry-on-nonzero')} Retry commands on nonzero exit status (default: false; overridden by .kdeploy.yml)
|
|
38
|
+
#{@pastel.dim(' --timeout SECONDS')} Per-host execution timeout seconds (default: none; overridden by .kdeploy.yml)
|
|
37
39
|
|
|
38
40
|
#{@pastel.bright_yellow('🆕')} #{@pastel.bright_white('init [DIR]')} Initialize new deployment project
|
|
39
41
|
#{@pastel.bright_yellow('ℹ️')} #{@pastel.bright_white('version')} Show version information
|
data/lib/kdeploy/runner.rb
CHANGED
|
@@ -7,7 +7,9 @@ module Kdeploy
|
|
|
7
7
|
class Runner
|
|
8
8
|
def initialize(hosts, tasks, parallel: Configuration.default_parallel, output: ConsoleOutput.new,
|
|
9
9
|
debug: false, base_dir: nil, retries: Configuration.default_retries,
|
|
10
|
-
retry_delay: Configuration.default_retry_delay
|
|
10
|
+
retry_delay: Configuration.default_retry_delay,
|
|
11
|
+
retry_on_nonzero: Configuration.default_retry_on_nonzero,
|
|
12
|
+
host_timeout: Configuration.default_host_timeout)
|
|
11
13
|
@hosts = hosts
|
|
12
14
|
@tasks = tasks
|
|
13
15
|
@parallel = parallel
|
|
@@ -16,6 +18,8 @@ module Kdeploy
|
|
|
16
18
|
@base_dir = base_dir
|
|
17
19
|
@retries = retries
|
|
18
20
|
@retry_delay = retry_delay
|
|
21
|
+
@retry_on_nonzero = retry_on_nonzero
|
|
22
|
+
@host_timeout = normalize_timeout(host_timeout)
|
|
19
23
|
@pool = Concurrent::FixedThreadPool.new(@parallel)
|
|
20
24
|
@results = Concurrent::Hash.new
|
|
21
25
|
end
|
|
@@ -41,59 +45,48 @@ module Kdeploy
|
|
|
41
45
|
# If no hosts, return empty results immediately
|
|
42
46
|
return @results if futures.empty?
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
status: :unknown,
|
|
64
|
-
error: "Unexpected result format: #{future_result.class}",
|
|
48
|
+
pending = futures.dup
|
|
49
|
+
|
|
50
|
+
until pending.empty?
|
|
51
|
+
progressed = false
|
|
52
|
+
now = Time.now
|
|
53
|
+
|
|
54
|
+
pending.dup.each do |future|
|
|
55
|
+
meta = @future_meta[future]
|
|
56
|
+
host_name = meta[:host_name]
|
|
57
|
+
started_at = meta[:started_at].get
|
|
58
|
+
|
|
59
|
+
if future.fulfilled? || future.rejected?
|
|
60
|
+
collect_future_result(future, host_name)
|
|
61
|
+
pending.delete(future)
|
|
62
|
+
progressed = true
|
|
63
|
+
elsif timeout_exceeded?(started_at, now)
|
|
64
|
+
@results[host_name] ||= {
|
|
65
|
+
status: :failed,
|
|
66
|
+
error: "execution timeout after #{@host_timeout}s",
|
|
65
67
|
output: []
|
|
66
68
|
}
|
|
69
|
+
pending.delete(future)
|
|
70
|
+
progressed = true
|
|
67
71
|
end
|
|
68
|
-
|
|
69
|
-
# Check if future raised an exception
|
|
70
|
-
if future.rejected?
|
|
71
|
-
error = begin
|
|
72
|
-
future.reason
|
|
73
|
-
rescue StandardError
|
|
74
|
-
'Unknown error'
|
|
75
|
-
end
|
|
76
|
-
@results[host_name] = { status: :failed, error: error, output: [] } unless @results.key?(host_name)
|
|
77
|
-
end
|
|
78
|
-
rescue StandardError => e
|
|
79
|
-
# If future.value raises an exception, create an error result
|
|
80
|
-
@results[host_name] = { status: :failed, error: "#{e.class}: #{e.message}", output: [] }
|
|
81
|
-
ensure
|
|
82
|
-
# Ensure we always have a result for this host
|
|
83
|
-
@results[host_name] ||= { status: :unknown, error: 'No result collected', output: [] }
|
|
84
72
|
end
|
|
73
|
+
|
|
74
|
+
sleep(0.05) unless progressed
|
|
85
75
|
end
|
|
86
76
|
|
|
87
77
|
@results
|
|
88
78
|
end
|
|
89
79
|
|
|
90
80
|
def create_task_futures(task, task_name)
|
|
91
|
-
|
|
92
|
-
@host_names = @hosts.keys
|
|
81
|
+
@future_meta = {}
|
|
93
82
|
@hosts.map do |name, config|
|
|
94
|
-
Concurrent::
|
|
83
|
+
started_at = Concurrent::AtomicReference.new(nil)
|
|
84
|
+
future = Concurrent::Future.execute(executor: @pool) do
|
|
85
|
+
started_at.set(Time.now)
|
|
95
86
|
execute_task_for_host(name, config, task, task_name)
|
|
96
87
|
end
|
|
88
|
+
@future_meta[future] = { host_name: name, started_at: started_at }
|
|
89
|
+
future
|
|
97
90
|
end
|
|
98
91
|
end
|
|
99
92
|
|
|
@@ -108,7 +101,8 @@ module Kdeploy
|
|
|
108
101
|
@output,
|
|
109
102
|
debug: @debug,
|
|
110
103
|
retries: @retries,
|
|
111
|
-
retry_delay: @retry_delay
|
|
104
|
+
retry_delay: @retry_delay,
|
|
105
|
+
retry_on_nonzero: @retry_on_nonzero
|
|
112
106
|
)
|
|
113
107
|
result = { status: :success, output: [] }
|
|
114
108
|
|
|
@@ -133,7 +127,7 @@ module Kdeploy
|
|
|
133
127
|
rescue StandardError => e
|
|
134
128
|
step = step_description(command)
|
|
135
129
|
result[:status] = :failed
|
|
136
|
-
result[:error] =
|
|
130
|
+
result[:error] = build_step_error(task_name, name, step, e)
|
|
137
131
|
result[:output] << {
|
|
138
132
|
type: command[:type],
|
|
139
133
|
command: step_command_string(command),
|
|
@@ -145,6 +139,53 @@ module Kdeploy
|
|
|
145
139
|
end
|
|
146
140
|
end
|
|
147
141
|
|
|
142
|
+
def collect_future_result(future, host_name)
|
|
143
|
+
return if @results.key?(host_name)
|
|
144
|
+
|
|
145
|
+
begin
|
|
146
|
+
future_result = future.value
|
|
147
|
+
if future_result.nil?
|
|
148
|
+
@results[host_name] = { status: :unknown, error: 'Future returned nil', output: [] }
|
|
149
|
+
elsif future_result.is_a?(Array) && future_result.length == 2
|
|
150
|
+
name, result = future_result
|
|
151
|
+
@results[name] = result
|
|
152
|
+
else
|
|
153
|
+
@results[host_name] = {
|
|
154
|
+
status: :unknown,
|
|
155
|
+
error: "Unexpected result format: #{future_result.class}",
|
|
156
|
+
output: []
|
|
157
|
+
}
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
if future.rejected?
|
|
161
|
+
error = begin
|
|
162
|
+
future.reason
|
|
163
|
+
rescue StandardError
|
|
164
|
+
'Unknown error'
|
|
165
|
+
end
|
|
166
|
+
@results[host_name] ||= { status: :failed, error: error, output: [] }
|
|
167
|
+
end
|
|
168
|
+
rescue StandardError => e
|
|
169
|
+
@results[host_name] = { status: :failed, error: "#{e.class}: #{e.message}", output: [] }
|
|
170
|
+
ensure
|
|
171
|
+
@results[host_name] ||= { status: :unknown, error: 'No result collected', output: [] }
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def timeout_exceeded?(started_at, now)
|
|
176
|
+
return false unless @host_timeout
|
|
177
|
+
return false if started_at.nil?
|
|
178
|
+
|
|
179
|
+
(now - started_at) > @host_timeout
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def normalize_timeout(timeout)
|
|
183
|
+
return nil if timeout.nil?
|
|
184
|
+
|
|
185
|
+
timeout = timeout.to_f
|
|
186
|
+
timeout.positive? ? timeout : nil
|
|
187
|
+
end
|
|
188
|
+
|
|
148
189
|
def error_output_for_step(error)
|
|
149
190
|
return nil unless error.is_a?(Kdeploy::SSHError)
|
|
150
191
|
|
|
@@ -156,6 +197,17 @@ module Kdeploy
|
|
|
156
197
|
}
|
|
157
198
|
end
|
|
158
199
|
|
|
200
|
+
def build_step_error(task_name, host_name, step, error)
|
|
201
|
+
base = "task=#{task_name} host=#{host_name} step=#{step} error=#{error.class}: #{error.message}"
|
|
202
|
+
return base unless error.is_a?(Kdeploy::SSHError)
|
|
203
|
+
|
|
204
|
+
if error.exit_status
|
|
205
|
+
"#{base} exit_status=#{error.exit_status} command=#{error.command}"
|
|
206
|
+
else
|
|
207
|
+
base
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
159
211
|
def execute_command(command_executor, command, host_name)
|
|
160
212
|
case command[:type]
|
|
161
213
|
when :run
|
data/lib/kdeploy/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kdeploy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kk
|
|
@@ -184,7 +184,6 @@ files:
|
|
|
184
184
|
- lib/kdeploy/runner.rb
|
|
185
185
|
- lib/kdeploy/template.rb
|
|
186
186
|
- lib/kdeploy/version.rb
|
|
187
|
-
- r.md
|
|
188
187
|
homepage: https://github.com/kevin197011/kdeploy
|
|
189
188
|
licenses:
|
|
190
189
|
- MIT
|