kdeploy 1.2.33 → 1.2.39

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.
data/README_EN.md CHANGED
@@ -97,6 +97,12 @@ kdeploy version
97
97
 
98
98
  You should see the version information and banner.
99
99
 
100
+ **If `kdeploy` is not found**: the gem executable directory may not be in your PATH. Add to `~/.zshrc` or `~/.bashrc` then run `source ~/.zshrc`:
101
+
102
+ ```bash
103
+ export PATH="$(ruby -e 'puts Gem.bindir'):$PATH"
104
+ ```
105
+
100
106
  ### Shell Completion
101
107
 
102
108
  Kdeploy automatically configures shell completion during installation. If needed, manually add to your shell config:
@@ -136,35 +142,30 @@ This creates a new directory with:
136
142
 
137
143
  ### 2. Configure Hosts and Tasks
138
144
 
139
- Edit `deploy.rb`:
145
+ Edit `deploy.rb` (using Chef-style resource DSL):
140
146
 
141
147
  ```ruby
142
148
  # Define hosts
143
149
  host "web01", user: "ubuntu", ip: "10.0.0.1", key: "~/.ssh/id_rsa"
144
150
  host "web02", user: "ubuntu", ip: "10.0.0.2", key: "~/.ssh/id_rsa"
145
-
146
- # Define roles
147
151
  role :web, %w[web01 web02]
148
152
 
149
153
  # Define deployment task
150
- task :deploy, roles: :web do
151
- run <<~SHELL
152
- sudo systemctl stop nginx
153
- echo "Deploying application..."
154
- SHELL
155
-
156
- upload_template "./config/nginx.conf.erb", "/etc/nginx/nginx.conf",
157
- domain_name: "example.com",
158
- port: 3000
159
-
160
- run "sudo systemctl start nginx"
154
+ task :deploy_web, roles: :web do
155
+ package "nginx"
156
+ directory "/etc/nginx/conf.d"
157
+ template "/etc/nginx/nginx.conf", source: "./config/nginx.conf.erb",
158
+ variables: { domain_name: "example.com", port: 3000 }
159
+ file "/etc/nginx/conf.d/app.conf", source: "./config/app.conf"
160
+ run "nginx -t", sudo: true
161
+ service "nginx", action: %i[enable restart]
161
162
  end
162
163
  ```
163
164
 
164
165
  ### 3. Run Deployment
165
166
 
166
167
  ```bash
167
- kdeploy execute deploy.rb deploy
168
+ kdeploy execute deploy.rb deploy_web
168
169
  ```
169
170
 
170
171
  ## 📖 Usage Guide
@@ -355,7 +356,7 @@ end
355
356
 
356
357
  ```ruby
357
358
  task :deploy_web, roles: :web do
358
- run "sudo systemctl restart nginx"
359
+ service "nginx", action: :restart
359
360
  end
360
361
  ```
361
362
 
@@ -363,29 +364,22 @@ end
363
364
 
364
365
  ```ruby
365
366
  task :maintenance, on: %w[web01] do
366
- run <<~SHELL
367
- sudo systemctl stop nginx
368
- sudo apt-get update && sudo apt-get upgrade -y
369
- sudo systemctl start nginx
370
- SHELL
367
+ service "nginx", action: :stop
368
+ run "apt-get update && apt-get upgrade -y", sudo: true
369
+ service "nginx", action: %i[start enable]
371
370
  end
372
371
  ```
373
372
 
374
373
  #### Task with Multiple Commands
375
374
 
376
375
  ```ruby
377
- task :deploy, roles: :web do
378
- # Stop service
379
- run "sudo systemctl stop nginx"
380
-
381
- # Upload configuration
382
- upload "./config/nginx.conf", "/etc/nginx/nginx.conf"
383
-
384
- # Start service
385
- run "sudo systemctl start nginx"
386
-
387
- # Verify status
388
- run "sudo systemctl status nginx"
376
+ task :deploy_web, roles: :web do
377
+ package "nginx"
378
+ directory "/etc/nginx/conf.d"
379
+ template "/etc/nginx/nginx.conf", source: "./config/nginx.conf.erb", variables: { port: 3000 }
380
+ file "/etc/nginx/conf.d/app.conf", source: "./config/app.conf"
381
+ run "nginx -t", sudo: true
382
+ service "nginx", action: %i[enable restart]
389
383
  end
390
384
  ```
391
385
 
@@ -486,6 +480,67 @@ upload_template "./config/nginx.conf.erb", "/etc/nginx/nginx.conf",
486
480
  - `destination`: Remote file path
487
481
  - `variables`: Hash of variables for template rendering
488
482
 
483
+ ### Chef-Style Resource DSL
484
+
485
+ Kdeploy provides a declarative resource DSL similar to Chef, which can replace or mix with low-level primitives (`run`, `upload`, `upload_template`).
486
+
487
+ #### `package` - Install System Packages
488
+
489
+ ```ruby
490
+ package "nginx"
491
+ package "nginx", version: "1.18"
492
+ package "nginx", platform: :yum # CentOS/RHEL
493
+ ```
494
+
495
+ Uses apt (Ubuntu/Debian) by default; `platform: :yum` generates yum commands.
496
+
497
+ #### `service` - Manage System Services (systemd)
498
+
499
+ ```ruby
500
+ service "nginx", action: [:enable, :start]
501
+ service "nginx", action: :restart
502
+ service "nginx", action: [:stop, :disable]
503
+ ```
504
+
505
+ Supports `:start`, `:stop`, `:restart`, `:reload`, `:enable`, `:disable`.
506
+
507
+ #### `template` - Deploy ERB Templates
508
+
509
+ ```ruby
510
+ template "/etc/nginx/nginx.conf", source: "./config/nginx.conf.erb", variables: { port: 3000 }
511
+ # Or block syntax
512
+ template "/etc/app.conf" do
513
+ source "./config/app.erb"
514
+ variables(domain: "example.com")
515
+ end
516
+ ```
517
+
518
+ #### `file` - Upload Local Files
519
+
520
+ ```ruby
521
+ file "/etc/nginx/conf.d/app.conf", source: "./config/app.conf"
522
+ ```
523
+
524
+ #### `directory` - Ensure Remote Directory Exists
525
+
526
+ ```ruby
527
+ directory "/etc/nginx/conf.d"
528
+ directory "/var/log/app", mode: "0755"
529
+ ```
530
+
531
+ **Example: Deploy Nginx Using Resource DSL**
532
+
533
+ ```ruby
534
+ task :deploy_nginx, roles: :web do
535
+ package "nginx"
536
+ directory "/etc/nginx/conf.d"
537
+ template "/etc/nginx/nginx.conf", source: "./config/nginx.conf.erb", variables: { port: 3000 }
538
+ file "/etc/nginx/conf.d/app.conf", source: "./config/app.conf"
539
+ run "nginx -t", sudo: true
540
+ service "nginx", action: %i[enable restart]
541
+ end
542
+ ```
543
+
489
544
  ### Template Support
490
545
 
491
546
  Kdeploy supports ERB (Embedded Ruby) templates for dynamic configuration generation.
@@ -532,11 +587,8 @@ http {
532
587
 
533
588
  ```ruby
534
589
  task :deploy_config do
535
- upload_template "./config/nginx.conf.erb", "/etc/nginx/nginx.conf",
536
- domain_name: "example.com",
537
- port: 3000,
538
- worker_processes: 4,
539
- worker_connections: 2048
590
+ template "/etc/nginx/nginx.conf", source: "./config/nginx.conf.erb",
591
+ variables: { domain_name: "example.com", port: 3000, worker_processes: 4, worker_connections: 2048 }
540
592
  end
541
593
  ```
542
594
 
@@ -597,15 +649,11 @@ Use Ruby conditionals in your deployment files:
597
649
 
598
650
  ```ruby
599
651
  task :deploy do
600
- if ENV['ENVIRONMENT'] == 'production'
601
- run "sudo systemctl stop nginx"
602
- end
652
+ service "nginx", action: :stop if ENV['ENVIRONMENT'] == 'production'
603
653
 
604
- upload "./config/nginx.conf", "/etc/nginx/nginx.conf"
654
+ file "/etc/nginx/nginx.conf", source: "./config/nginx.conf"
605
655
 
606
- if ENV['ENVIRONMENT'] == 'production'
607
- run "sudo systemctl start nginx"
608
- end
656
+ service "nginx", action: :start if ENV['ENVIRONMENT'] == 'production'
609
657
  end
610
658
  ```
611
659
 
@@ -628,9 +676,10 @@ end
628
676
 
629
677
  ```ruby
630
678
  task :deploy do
631
- run "sudo systemctl stop nginx" || raise "Failed to stop nginx"
632
- upload "./config/nginx.conf", "/etc/nginx/nginx.conf"
633
- run "sudo systemctl start nginx" || raise "Failed to start nginx"
679
+ service "nginx", action: :stop
680
+ file "/etc/nginx/nginx.conf", source: "./config/nginx.conf"
681
+ run "nginx -t", sudo: true # run raises on invalid config
682
+ service "nginx", action: :start
634
683
  end
635
684
  ```
636
685
 
@@ -746,10 +795,9 @@ end
746
795
  ### 3. Use Templates for Dynamic Configuration
747
796
 
748
797
  ```ruby
749
- # ✅ Good - Use templates
750
- upload_template "./config/nginx.conf.erb", "/etc/nginx/nginx.conf",
751
- domain_name: "example.com",
752
- port: 3000
798
+ # ✅ Good - Use template resource
799
+ template "/etc/nginx/nginx.conf", source: "./config/nginx.conf.erb",
800
+ variables: { domain_name: "example.com", port: 3000 }
753
801
 
754
802
  # ❌ Avoid - Hardcoding values
755
803
  run "echo 'server_name example.com;' > /etc/nginx/nginx.conf"
@@ -759,12 +807,10 @@ run "echo 'server_name example.com;' > /etc/nginx/nginx.conf"
759
807
 
760
808
  ```ruby
761
809
  task :deploy do
762
- # Validate configuration
763
- run "nginx -t" || raise "Nginx configuration is invalid"
764
-
765
- # Deploy
766
- upload "./config/nginx.conf", "/etc/nginx/nginx.conf"
767
- run "sudo systemctl reload nginx"
810
+ template "/etc/nginx/nginx.conf", source: "./config/nginx.conf.erb", variables: { port: 3000 }
811
+ file "/etc/nginx/conf.d/app.conf", source: "./config/app.conf"
812
+ run "nginx -t", sudo: true # run raises on invalid config
813
+ service "nginx", action: :reload
768
814
  end
769
815
  ```
770
816
 
@@ -883,7 +929,6 @@ Enable verbose output by checking the execution output. Kdeploy provides detaile
883
929
  - **Executor** (`executor.rb`): SSH/SCP execution engine
884
930
  - **Runner** (`runner.rb`): Concurrent task execution coordinator
885
931
  - **CommandExecutor** (`command_executor.rb`): Individual command execution
886
- - **CommandGrouper** (`command_grouper.rb`): Command grouping logic
887
932
  - **Template** (`template.rb`): ERB template rendering
888
933
  - **Output** (`output.rb`): Output formatting and display
889
934
  - **Configuration** (`configuration.rb`): Configuration management
@@ -893,10 +938,9 @@ Enable verbose output by checking the execution output. Kdeploy provides detaile
893
938
 
894
939
  1. **Parse Configuration**: Load and parse `deploy.rb`
895
940
  2. **Resolve Hosts**: Determine target hosts based on task definition
896
- 3. **Group Commands**: Group commands by type for efficient execution
897
- 4. **Execute Concurrently**: Run tasks in parallel across hosts
898
- 5. **Collect Results**: Gather execution results and status
899
- 6. **Display Output**: Format and display results to user
941
+ 3. **Execute Concurrently**: Run tasks in parallel across hosts, executing commands in order per host
942
+ 4. **Collect Results**: Gather execution results and status
943
+ 5. **Display Output**: Format and display results to user
900
944
 
901
945
  ### Concurrency Model
902
946
 
@@ -936,7 +980,6 @@ kdeploy/
936
980
  │ ├── executor.rb # SSH/SCP executor
937
981
  │ ├── runner.rb # Task runner
938
982
  │ ├── command_executor.rb # Command executor
939
- │ ├── command_grouper.rb # Command grouper
940
983
  │ ├── template.rb # Template handler
941
984
  │ ├── output.rb # Output interface
942
985
  │ ├── configuration.rb # Configuration
@@ -1018,9 +1061,16 @@ Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
1018
1061
 
1019
1062
  ## 📚 Examples
1020
1063
 
1021
- ### Example Projects
1064
+ ### Example Project
1022
1065
 
1023
- Check out the [example project](https://github.com/kevin197011/kdeploy-app) for a complete deployment setup.
1066
+ The [sample/](sample/) directory in this repo provides a complete example with Nginx, Node Exporter, directory sync tasks, and Vagrant support for local testing:
1067
+
1068
+ ```bash
1069
+ cd sample
1070
+ vagrant up
1071
+ kdeploy execute deploy.rb deploy_web --dry-run # Preview
1072
+ kdeploy execute deploy.rb deploy_web # Execute
1073
+ ```
1024
1074
 
1025
1075
  ### Common Deployment Scenarios
1026
1076
 
@@ -1061,12 +1111,21 @@ end
1061
1111
 
1062
1112
  ```ruby
1063
1113
  task :update_config, roles: :web do
1064
- upload_template "./config/app.yml.erb", "/etc/app/config.yml",
1065
- environment: "production",
1066
- database_url: ENV['DATABASE_URL'],
1067
- redis_url: ENV['REDIS_URL']
1114
+ template "/etc/app/config.yml", source: "./config/app.yml.erb",
1115
+ variables: { environment: "production", database_url: ENV['DATABASE_URL'], redis_url: ENV['REDIS_URL'] }
1116
+ service "app", action: :reload
1117
+ end
1118
+ ```
1068
1119
 
1069
- run "sudo systemctl reload app"
1120
+ #### Directory Sync Deployment
1121
+
1122
+ ```ruby
1123
+ task :deploy_app, roles: :web do
1124
+ sync "./app", "/var/www/app",
1125
+ ignore: [".git", "*.log", "node_modules", ".env.local", "*.tmp"],
1126
+ delete: true
1127
+ sync "./config", "/etc/app", exclude: ["*.example", "*.bak"]
1128
+ service "app", action: :restart
1070
1129
  end
1071
1130
  ```
1072
1131
 
@@ -1079,7 +1138,7 @@ The gem is available as open source under the terms of the [MIT License](https:/
1079
1138
  - **GitHub**: https://github.com/kevin197011/kdeploy
1080
1139
  - **RubyGems**: https://rubygems.org/gems/kdeploy
1081
1140
  - **Issues**: https://github.com/kevin197011/kdeploy/issues
1082
- - **Example Project**: https://github.com/kevin197011/kdeploy-app
1141
+ - **Sample**: [sample/](sample/) directory (includes Vagrant config)
1083
1142
 
1084
1143
  ## 🙏 Acknowledgments
1085
1144
 
data/lib/kdeploy/cli.rb CHANGED
@@ -142,21 +142,24 @@ module Kdeploy
142
142
  print_summary(results, formatter) if show_summary
143
143
  end
144
144
 
145
- def print_host_result(_host, result, formatter)
145
+ def print_host_result(host, result, formatter)
146
146
  if %i[success changed].include?(result[:status])
147
- print_success_result(result, formatter)
147
+ print_success_result(host, result, formatter)
148
148
  else
149
- print_failure_result(result, formatter)
149
+ print_failure_result(host, result, formatter)
150
150
  end
151
+
152
+ duration = formatter.calculate_host_duration(result)
153
+ puts "#{formatter.host_prefix(host)}#{formatter.format_host_completed(duration)}" if duration.positive?
151
154
  end
152
155
 
153
- def print_success_result(result, formatter)
156
+ def print_success_result(host, result, formatter)
154
157
  shown = {}
155
158
  grouped = group_output_by_type(result[:output])
156
159
 
157
160
  grouped.each do |type, steps|
158
161
  output_lines = format_steps_by_type(type, steps, shown, formatter)
159
- output_lines.each { |line| puts line }
162
+ output_lines.each { |line| puts "#{formatter.host_prefix(host)}#{line}" }
160
163
  end
161
164
  end
162
165
 
@@ -179,9 +182,21 @@ module Kdeploy
179
182
  end
180
183
  end
181
184
 
182
- def print_failure_result(result, formatter)
185
+ def print_failure_result(host, result, formatter)
183
186
  error_message = extract_error_message(result)
184
- puts formatter.format_error(error_message)
187
+ puts "#{formatter.host_prefix(host)}#{formatter.format_error(error_message)}"
188
+
189
+ # On failure, show steps that were executed (and any captured output)
190
+ # to make troubleshooting easier, even if --debug is not enabled.
191
+ return unless result[:output].is_a?(Array) && result[:output].any?
192
+
193
+ debug_formatter = OutputFormatter.new(debug: true)
194
+ shown = {}
195
+ grouped = group_output_by_type(result[:output])
196
+ grouped.each do |type, steps|
197
+ output_lines = format_steps_by_type(type, steps, shown, debug_formatter)
198
+ output_lines.each { |line| puts "#{debug_formatter.host_prefix(host)}#{line}" }
199
+ end
185
200
  end
186
201
 
187
202
  def print_summary(results, formatter)
@@ -214,6 +229,9 @@ module Kdeploy
214
229
  task_results = execute_single_task(task)
215
230
  # Collect results for final summary
216
231
  all_results[task] = task_results if task_results
232
+
233
+ # Stop executing remaining tasks once any host failed for this task.
234
+ break if task_failed?(task_results)
217
235
  end
218
236
 
219
237
  # Show combined summary at the end for all tasks
@@ -275,6 +293,12 @@ module Kdeploy
275
293
  end
276
294
  end
277
295
 
296
+ def task_failed?(task_results)
297
+ return false unless task_results.is_a?(Hash)
298
+
299
+ task_results.values.any? { |result| result[:status] == :failed }
300
+ end
301
+
278
302
  def print_all_tasks_summary(all_results)
279
303
  debug_mode = options[:debug] || false
280
304
  formatter = OutputFormatter.new(debug: debug_mode)
@@ -11,26 +11,18 @@ module Kdeploy
11
11
  @retry_delay = retry_delay.to_f
12
12
  end
13
13
 
14
- def execute_run(command, host_name)
14
+ def execute_run(command, _host_name)
15
15
  cmd = command[:command]
16
16
  use_sudo = command[:sudo]
17
- show_command_header(host_name, :run, cmd)
18
-
19
- # Show progress indicator for long-running commands
20
- pastel = @output.respond_to?(:pastel) ? @output.pastel : Pastel.new
21
17
 
22
18
  result, duration = measure_time do
23
19
  with_retries { @executor.execute(cmd, use_sudo: use_sudo) }
24
20
  end
25
21
 
26
- # Show execution time if command took more than 1 second
27
- @output.write_line(pastel.dim(" [completed in #{format('%.2f', duration)}s]")) if duration > 1.0
28
-
29
22
  { command: cmd, output: result, duration: duration, type: :run }
30
23
  end
31
24
 
32
- def execute_upload(command, host_name)
33
- show_command_header(host_name, :upload, "#{command[:source]} -> #{command[:destination]}")
25
+ def execute_upload(command, _host_name)
34
26
  _result, duration = measure_time do
35
27
  with_retries { @executor.upload(command[:source], command[:destination]) }
36
28
  end
@@ -41,8 +33,7 @@ module Kdeploy
41
33
  }
42
34
  end
43
35
 
44
- def execute_upload_template(command, host_name)
45
- show_command_header(host_name, :upload_template, "#{command[:source]} -> #{command[:destination]}")
36
+ def execute_upload_template(command, _host_name)
46
37
  _result, duration = measure_time do
47
38
  with_retries do
48
39
  @executor.upload_template(command[:source], command[:destination], command[:variables])
@@ -55,11 +46,9 @@ module Kdeploy
55
46
  }
56
47
  end
57
48
 
58
- def execute_sync(command, host_name)
49
+ def execute_sync(command, _host_name)
59
50
  source = command[:source]
60
51
  destination = command[:destination]
61
- description = build_sync_description(source, destination, command[:delete])
62
- show_command_header(host_name, :sync, description)
63
52
 
64
53
  result, duration = measure_time do
65
54
  with_retries do
@@ -78,12 +67,6 @@ module Kdeploy
78
67
 
79
68
  private
80
69
 
81
- def build_sync_description(source, destination, delete)
82
- desc = "sync: #{source} -> #{destination}"
83
- desc += " (delete: #{delete})" if delete
84
- desc
85
- end
86
-
87
70
  def build_sync_result(source, destination, result, duration)
88
71
  {
89
72
  command: "sync: #{source} -> #{destination}",
@@ -115,32 +98,5 @@ module Kdeploy
115
98
  retry
116
99
  end
117
100
  end
118
-
119
- def show_command_header(host_name, type, description)
120
- # Don't show command header during execution - it will be shown in results
121
- # This reduces noise during execution
122
- end
123
-
124
- def pastel_instance
125
- @output.respond_to?(:pastel) ? @output.pastel : Pastel.new
126
- end
127
-
128
- def format_command_by_type(type, description, pastel)
129
- case type
130
- when :run
131
- format_run_command(description, pastel)
132
- when :upload
133
- @output.write_line(pastel.green(" [upload] #{description}"))
134
- when :upload_template
135
- @output.write_line(pastel.yellow(" [template] #{description}"))
136
- end
137
- end
138
-
139
- def format_run_command(description, pastel)
140
- @output.write_line(pastel.cyan(" [run] #{description.lines.first.strip}"))
141
- description.lines[1..].each do |line|
142
- @output.write_line(" > #{line.strip}") unless line.strip.empty?
143
- end
144
- end
145
101
  end
146
102
  end
data/lib/kdeploy/dsl.rb CHANGED
@@ -1,8 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'set'
4
+ require 'shellwords'
4
5
 
5
6
  module Kdeploy
7
+ # Helper for template resource block: captures source and variables
8
+ class TemplateOptions
9
+ def initialize
10
+ @source = nil
11
+ @variables = {}
12
+ end
13
+
14
+ def variables(val = nil)
15
+ @variables = val if val
16
+ @variables
17
+ end
18
+
19
+ def source(val = nil)
20
+ @source = val if val
21
+ @source
22
+ end
23
+ end
24
+
6
25
  # Domain-specific language for defining hosts, roles, and tasks
7
26
  module DSL
8
27
  def self.included(base)
@@ -154,6 +173,60 @@ module Kdeploy
154
173
  }
155
174
  end
156
175
 
176
+ # -------------------------------------------------------------------------
177
+ # Chef-style resource DSL (compiles to run/upload/upload_template)
178
+ # -------------------------------------------------------------------------
179
+
180
+ # 安装系统包。默认 apt 平台;支持 platform: :yum。
181
+ def package(name, version: nil, platform: :apt)
182
+ @kdeploy_commands ||= []
183
+ cmd = build_package_command(name, version, platform)
184
+ @kdeploy_commands << { type: :run, command: cmd, sudo: true }
185
+ end
186
+
187
+ # 管理系统服务(systemd)。action 支持 :start, :stop, :restart, :reload, :enable, :disable。
188
+ def service(name, action: :start)
189
+ @kdeploy_commands ||= []
190
+ actions = Array(action)
191
+ actions.each do |a|
192
+ cmd = "systemctl #{a} #{Shellwords.escape(name.to_s)}"
193
+ @kdeploy_commands << { type: :run, command: cmd, sudo: true }
194
+ end
195
+ end
196
+
197
+ # 部署 ERB 模板到远程路径。支持 block 或关键字参数。
198
+ def template(destination, source: nil, variables: nil, &block)
199
+ @kdeploy_commands ||= []
200
+ if block
201
+ opts = TemplateOptions.new
202
+ opts.instance_eval(&block)
203
+ src = opts.source || raise(ArgumentError, 'template requires source')
204
+ vars = opts.variables || {}
205
+ else
206
+ raise ArgumentError, 'template requires source' unless source
207
+
208
+ src = source
209
+ vars = variables || {}
210
+ end
211
+ upload_template(src, destination, vars)
212
+ end
213
+
214
+ # 上传本地文件到远程路径。
215
+ def file(destination, source:)
216
+ @kdeploy_commands ||= []
217
+ upload(source, destination)
218
+ end
219
+
220
+ # 确保远程目录存在。支持 mode 参数。
221
+ def directory(path, mode: nil)
222
+ @kdeploy_commands ||= []
223
+ cmd = "mkdir -p #{Shellwords.escape(path.to_s)}"
224
+ @kdeploy_commands << { type: :run, command: cmd, sudo: true }
225
+ return unless mode
226
+
227
+ @kdeploy_commands << { type: :run, command: "chmod #{mode} #{Shellwords.escape(path.to_s)}", sudo: true }
228
+ end
229
+
157
230
  def inventory(&block)
158
231
  instance_eval(&block) if block_given?
159
232
  end
@@ -188,5 +261,20 @@ module Kdeploy
188
261
  end
189
262
  end
190
263
  end
264
+
265
+ def build_package_command(name, version, platform)
266
+ n = Shellwords.escape(name.to_s)
267
+ case platform.to_sym
268
+ when :yum, :rpm
269
+ if version
270
+ "yum install -y #{n}-#{Shellwords.escape(version.to_s)}"
271
+ else
272
+ "yum install -y #{n}"
273
+ end
274
+ else
275
+ base = "apt-get update && apt-get install -y #{n}"
276
+ version ? "#{base}=#{Shellwords.escape(version.to_s)}" : base
277
+ end
278
+ end
191
279
  end
192
280
  end
@@ -20,12 +20,16 @@ module Kdeploy
20
20
 
21
21
  # Raised when SSH operation fails
22
22
  class SSHError < Error
23
- def initialize(message, original_error = nil)
23
+ def initialize(message, original_error = nil, command: nil, exit_status: nil, stdout: nil, stderr: nil)
24
24
  super("SSH operation failed: #{message}")
25
25
  @original_error = original_error
26
+ @command = command
27
+ @exit_status = exit_status
28
+ @stdout = stdout
29
+ @stderr = stderr
26
30
  end
27
31
 
28
- attr_reader :original_error
32
+ attr_reader :original_error, :command, :exit_status, :stdout, :stderr
29
33
  end
30
34
 
31
35
  # Raised when SCP operation fails