kdeploy 0.1.0 ā 0.3.0
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 +101 -936
- data/exe/kdeploy +6 -0
- data/k.md +149 -0
- data/lib/kdeploy/banner.rb +44 -14
- data/lib/kdeploy/cli.rb +138 -1389
- data/lib/kdeploy/dsl.rb +66 -530
- data/lib/kdeploy/executor.rb +73 -0
- data/lib/kdeploy/initializer.rb +229 -0
- data/lib/kdeploy/runner.rb +40 -180
- data/lib/kdeploy/template.rb +18 -161
- data/lib/kdeploy/version.rb +1 -2
- data/lib/kdeploy.rb +9 -100
- metadata +75 -52
- data/.editorconfig +0 -12
- data/.rspec +0 -3
- data/.rubocop.yml +0 -100
- data/LICENSE +0 -21
- data/Rakefile +0 -45
- data/bin/kdeploy +0 -7
- data/kdeploy.gemspec +0 -49
- data/lib/kdeploy/command.rb +0 -182
- data/lib/kdeploy/configuration.rb +0 -83
- data/lib/kdeploy/host.rb +0 -85
- data/lib/kdeploy/inventory.rb +0 -243
- data/lib/kdeploy/logger.rb +0 -100
- data/lib/kdeploy/pipeline.rb +0 -249
- data/lib/kdeploy/ssh_connection.rb +0 -187
- data/lib/kdeploy/statistics.rb +0 -439
- data/lib/kdeploy/task.rb +0 -240
- data/scripts/common_tasks.rb +0 -218
- data/scripts/deploy.rb +0 -50
data/lib/kdeploy/cli.rb
CHANGED
@@ -1,1452 +1,201 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
"#{self[0, length - 3]}..."
|
9
|
-
end
|
10
|
-
end
|
3
|
+
require 'thor'
|
4
|
+
require 'pastel'
|
5
|
+
require 'tty-table'
|
6
|
+
require 'tty-box'
|
7
|
+
require 'fileutils'
|
11
8
|
|
12
9
|
module Kdeploy
|
13
|
-
# Command Line Interface for kdeploy
|
14
10
|
class CLI < Thor
|
15
|
-
|
11
|
+
extend DSL
|
12
|
+
|
16
13
|
def self.exit_on_failure?
|
17
14
|
true
|
18
15
|
end
|
19
16
|
|
20
|
-
|
21
|
-
|
22
|
-
config: { aliases: '-c', desc: 'Configuration file path' },
|
23
|
-
inventory: { aliases: '-i', desc: 'Inventory file path' },
|
24
|
-
dry_run: { aliases: '-d', type: :boolean, desc: 'Perform dry run without executing' },
|
25
|
-
verbose: { aliases: '-v', type: :boolean, desc: 'Enable verbose output' },
|
26
|
-
log_file: { aliases: '-l', desc: 'Log file path' }
|
27
|
-
}.freeze
|
28
|
-
|
29
|
-
desc 'execute SCRIPT', 'Execute deployment script'
|
30
|
-
SCRIPT_OPTIONS.each { |name, opts| option(name, opts) }
|
31
|
-
def execute(script_file)
|
32
|
-
setup_configuration
|
33
|
-
setup_logging
|
34
|
-
|
35
|
-
validate_script_file(script_file)
|
36
|
-
|
37
|
-
begin
|
38
|
-
options[:dry_run] ? perform_dry_run(script_file) : execute_script(script_file)
|
39
|
-
rescue Kdeploy::Error => e
|
40
|
-
handle_deployment_error(e)
|
41
|
-
rescue StandardError => e
|
42
|
-
handle_unexpected_error(e)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
desc 'deploy SCRIPT', 'Execute deployment script (alias for execute)'
|
47
|
-
SCRIPT_OPTIONS.each { |name, opts| option(name, opts) }
|
48
|
-
def deploy(script_file)
|
49
|
-
execute(script_file)
|
50
|
-
end
|
51
|
-
|
52
|
-
desc 'init [PROJECT_NAME]', 'Initialize new deployment project'
|
53
|
-
option :name, aliases: '-n', desc: 'Specify project name'
|
54
|
-
def init(project_name = nil)
|
55
|
-
show_kdeploy_banner
|
56
|
-
project_name = determine_project_name(project_name)
|
57
|
-
|
58
|
-
display_init_header(project_name)
|
59
|
-
create_project_structure(project_name)
|
60
|
-
display_init_success(project_name)
|
61
|
-
end
|
62
|
-
|
63
|
-
desc 'validate SCRIPT', 'Validate deployment script'
|
64
|
-
option :config, aliases: '-c', desc: 'Configuration file path'
|
65
|
-
option :inventory, aliases: '-i', desc: 'Inventory file path'
|
66
|
-
def validate(script_file)
|
67
|
-
show_kdeploy_banner
|
68
|
-
setup_configuration
|
69
|
-
|
70
|
-
display_validation_header(script_file)
|
71
|
-
validate_script_file(script_file)
|
72
|
-
|
73
|
-
begin
|
74
|
-
pipeline = Kdeploy.load_script(script_file)
|
75
|
-
validation_errors = pipeline.validate
|
76
|
-
|
77
|
-
if validation_errors.empty?
|
78
|
-
display_validation_success(pipeline)
|
79
|
-
else
|
80
|
-
display_validation_errors(validation_errors)
|
81
|
-
end
|
82
|
-
rescue Kdeploy::Error => e
|
83
|
-
display_validation_failure(e)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
desc 'config', 'Show configuration'
|
88
|
-
option :config, aliases: '-c', desc: 'Configuration file path'
|
89
|
-
option :inventory, aliases: '-i', desc: 'Inventory file path'
|
90
|
-
option :verbose, aliases: '-v', type: :boolean, desc: 'Enable verbose output'
|
91
|
-
option :log_file, aliases: '-l', desc: 'Log file path'
|
92
|
-
def config
|
93
|
-
show_kdeploy_banner
|
94
|
-
setup_configuration
|
95
|
-
|
96
|
-
display_configuration(Kdeploy.configuration)
|
97
|
-
end
|
17
|
+
map %w[--help -h] => :help
|
18
|
+
map %w[--version -v] => :version
|
98
19
|
|
99
|
-
desc 'version', 'Show version'
|
20
|
+
desc 'version', 'Show version information'
|
100
21
|
def version
|
101
|
-
|
102
|
-
display_version_info
|
22
|
+
puts Kdeploy::Banner.show_version
|
103
23
|
end
|
104
24
|
|
105
|
-
desc 'help [COMMAND]', '
|
25
|
+
desc 'help [COMMAND]', 'Show help information'
|
106
26
|
def help(command = nil)
|
107
|
-
show_kdeploy_banner
|
108
|
-
puts ''
|
109
|
-
puts 'š Available Commands:'.colorize(:yellow)
|
110
|
-
puts ''
|
111
|
-
|
112
27
|
if command
|
113
28
|
super
|
114
29
|
else
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
desc 'stats [COMMAND]', 'Show deployment statistics'
|
120
|
-
option :days, aliases: '-d', type: :numeric, default: 30, desc: 'Number of days to analyze'
|
121
|
-
option :format, aliases: '-f', type: :string, default: 'text', desc: 'Output format (text, json, csv)'
|
122
|
-
option :output, aliases: '-o', type: :string, desc: 'Output file path'
|
123
|
-
def stats(command = 'summary')
|
124
|
-
case command.downcase
|
125
|
-
when 'summary'
|
126
|
-
show_summary_stats
|
127
|
-
when 'deployments'
|
128
|
-
show_deployment_stats
|
129
|
-
when 'tasks'
|
130
|
-
show_task_stats
|
131
|
-
when 'failures'
|
132
|
-
show_failure_stats
|
133
|
-
when 'trends'
|
134
|
-
show_trend_stats
|
135
|
-
when 'global'
|
136
|
-
show_global_stats
|
137
|
-
when 'clear'
|
138
|
-
clear_statistics
|
139
|
-
when 'export'
|
140
|
-
export_statistics
|
141
|
-
else
|
142
|
-
error "Unknown stats command: #{command}"
|
143
|
-
puts ''
|
144
|
-
puts 'Available commands: summary, deployments, tasks, failures, trends, global, clear, export'
|
145
|
-
exit 1
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
private
|
150
|
-
|
151
|
-
def display_configuration(config)
|
152
|
-
puts ''
|
153
|
-
puts 'āļø Current Configuration'.colorize(:yellow)
|
154
|
-
puts '=' * 50
|
30
|
+
pastel = Pastel.new
|
31
|
+
puts Kdeploy::Banner.show
|
32
|
+
puts <<~HELP
|
33
|
+
#{pastel.bright_white('š Available Commands:')}
|
155
34
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
display_ssh_options(config) if config.respond_to?(:ssh_options) && config.ssh_options&.any?
|
35
|
+
#{pastel.bright_yellow('š')} #{pastel.bright_white('execute TASK_FILE [TASK]')} Execute deployment tasks from file
|
36
|
+
#{pastel.dim(' --limit HOSTS')} Limit to specific hosts (comma-separated)
|
37
|
+
#{pastel.dim(' --parallel NUM')} Number of parallel executions (default: 5)
|
38
|
+
#{pastel.dim(' --dry-run')} Show what would be done without executing
|
161
39
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
end
|
40
|
+
#{pastel.bright_yellow('š')} #{pastel.bright_white('init [DIR]')} Initialize new deployment project
|
41
|
+
#{pastel.bright_yellow('ā¹ļø')} #{pastel.bright_white('version')} Show version information
|
42
|
+
#{pastel.bright_yellow('ā')} #{pastel.bright_white('help [COMMAND]')} Show help information
|
166
43
|
|
167
|
-
|
168
|
-
puts ''
|
169
|
-
puts 'š§ Execution Settings'.colorize(:cyan)
|
170
|
-
puts " Max Concurrent Tasks: #{config.max_concurrent_tasks}".colorize(:light_blue)
|
171
|
-
puts " Retry Count: #{config.retry_count}".colorize(:light_blue)
|
172
|
-
puts " Retry Delay: #{config.retry_delay}s".colorize(:light_blue)
|
173
|
-
end
|
44
|
+
#{pastel.bright_white('š” Examples:')}
|
174
45
|
|
175
|
-
|
176
|
-
|
177
|
-
puts 'š Network & SSH Settings'.colorize(:cyan)
|
178
|
-
puts " SSH Timeout: #{config.ssh_timeout}s".colorize(:light_blue)
|
179
|
-
puts " Command Timeout: #{config.command_timeout}s".colorize(:light_blue)
|
180
|
-
puts " Default User: #{config.default_user}".colorize(:light_blue)
|
181
|
-
puts " Default Port: #{config.default_port}".colorize(:light_blue)
|
182
|
-
end
|
46
|
+
#{pastel.dim('# Initialize a new project')}
|
47
|
+
#{pastel.bright_cyan('kdeploy init my-deployment')}
|
183
48
|
|
184
|
-
|
185
|
-
|
186
|
-
puts 'š File & Directory Settings'.colorize(:cyan)
|
187
|
-
puts " Inventory File: #{config.inventory_file || 'not specified'}".colorize(:light_blue)
|
188
|
-
puts " Template Directory: #{config.template_dir}".colorize(:light_blue)
|
189
|
-
end
|
49
|
+
#{pastel.dim('# Deploy to web servers')}
|
50
|
+
#{pastel.bright_cyan('kdeploy execute deploy.rb deploy_web')}
|
190
51
|
|
191
|
-
|
192
|
-
|
193
|
-
puts 'š Logging Settings'.colorize(:cyan)
|
194
|
-
puts " Log Level: #{config.log_level}".colorize(:light_blue)
|
195
|
-
puts " Log File: #{config.log_file || 'stdout'}".colorize(:light_blue)
|
196
|
-
end
|
52
|
+
#{pastel.dim('# Backup database')}
|
53
|
+
#{pastel.bright_cyan('kdeploy execute deploy.rb backup_db')}
|
197
54
|
|
198
|
-
|
199
|
-
|
200
|
-
puts 'š SSH Options'.colorize(:cyan)
|
201
|
-
config.ssh_options.each do |key, value|
|
202
|
-
puts " #{key.to_s.capitalize.gsub('_', ' ')}: #{value}".colorize(:light_blue)
|
203
|
-
end
|
204
|
-
end
|
55
|
+
#{pastel.dim('# Run maintenance on specific hosts')}
|
56
|
+
#{pastel.bright_cyan('kdeploy execute deploy.rb maintenance --limit web01')}
|
205
57
|
|
206
|
-
|
207
|
-
|
208
|
-
puts "š Version: #{Kdeploy::VERSION}".colorize(:green)
|
209
|
-
puts 'š
Released: 2025'.colorize(:light_blue)
|
210
|
-
puts 'š Homepage: https://github.com/kevin197011/kdeploy'.colorize(:light_blue)
|
211
|
-
puts 'š Documentation: https://github.com/kevin197011/kdeploy/wiki'.colorize(:light_blue)
|
212
|
-
puts ''
|
213
|
-
end
|
58
|
+
#{pastel.dim('# Preview deployment')}
|
59
|
+
#{pastel.bright_cyan('kdeploy execute deploy.rb deploy_web --dry-run')}
|
214
60
|
|
215
|
-
|
216
|
-
|
217
|
-
puts " ā” #{'execute SCRIPT'.ljust(25)} Execute deployment script (alias for deploy)"
|
218
|
-
puts " š #{'init [PROJECT_NAME]'.ljust(25)} Initialize new deployment project"
|
219
|
-
puts " ā
#{'validate SCRIPT'.ljust(25)} Validate deployment script"
|
220
|
-
puts " āļø #{'config'.ljust(25)} Show configuration"
|
221
|
-
puts " š #{'stats [COMMAND]'.ljust(25)} Show deployment statistics"
|
222
|
-
puts " š #{'version'.ljust(25)} Show version"
|
223
|
-
puts ''
|
224
|
-
puts 'š” For more details on a command:'.colorize(:yellow)
|
225
|
-
puts ' kdeploy help COMMAND'.colorize(:light_blue)
|
226
|
-
puts ''
|
227
|
-
end
|
61
|
+
#{pastel.bright_white('š Documentation:')}
|
62
|
+
#{pastel.bright_cyan('https://github.com/kevin197011/kdeploy')}
|
228
63
|
|
229
|
-
|
230
|
-
Kdeploy.configure do |config|
|
231
|
-
config.config_file = options[:config] if options[:config]
|
232
|
-
config.inventory_file = options[:inventory] if options[:inventory]
|
233
|
-
config.log_level = options[:verbose] ? :debug : :info
|
234
|
-
config.log_file = options[:log_file] if options[:log_file]
|
64
|
+
HELP
|
235
65
|
end
|
236
66
|
end
|
237
67
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
)
|
244
|
-
|
245
|
-
|
246
|
-
def perform_dry_run(script_file)
|
247
|
-
info 'š Performing dry run...'
|
248
|
-
pipeline = Kdeploy.load_script(script_file)
|
249
|
-
|
250
|
-
puts ''
|
251
|
-
puts 'š Pipeline Summary:'.colorize(:cyan)
|
252
|
-
puts " Name: #{pipeline.name}".colorize(:light_blue)
|
253
|
-
puts " Hosts: #{pipeline.hosts.size}".colorize(:light_blue)
|
254
|
-
puts " Tasks: #{pipeline.tasks.size}".colorize(:light_blue)
|
255
|
-
|
256
|
-
display_target_hosts(pipeline.hosts) if pipeline.hosts.any?
|
257
|
-
display_tasks(pipeline.tasks) if pipeline.tasks.any?
|
258
|
-
|
259
|
-
success 'ā
Dry run completed'
|
260
|
-
end
|
261
|
-
|
262
|
-
def execute_script(script_file)
|
263
|
-
show_kdeploy_banner
|
264
|
-
info "š Executing deployment script: #{script_file}"
|
265
|
-
|
266
|
-
pipeline = Kdeploy.load_script(script_file)
|
267
|
-
result = pipeline.execute
|
268
|
-
|
269
|
-
if result[:success]
|
270
|
-
success 'ā
Deployment completed successfully'
|
271
|
-
else
|
272
|
-
error 'ā Deployment failed'
|
273
|
-
exit 1
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
def show_kdeploy_banner
|
278
|
-
Banner.show
|
279
|
-
end
|
280
|
-
|
281
|
-
def info(message)
|
282
|
-
puts message.colorize(:blue)
|
283
|
-
end
|
284
|
-
|
285
|
-
def success(message)
|
286
|
-
puts message.colorize(:green)
|
287
|
-
end
|
288
|
-
|
289
|
-
def error(message)
|
290
|
-
puts message.colorize(:red)
|
291
|
-
end
|
292
|
-
|
293
|
-
def create_project_structure(project_name)
|
294
|
-
create_project_directories(project_name)
|
295
|
-
create_sample_files(project_name)
|
296
|
-
create_sample_scripts(project_name)
|
297
|
-
create_sample_templates(project_name)
|
298
|
-
end
|
299
|
-
|
300
|
-
def create_project_directories(project_name)
|
301
|
-
dirs = [
|
302
|
-
project_name,
|
303
|
-
"#{project_name}/config",
|
304
|
-
"#{project_name}/scripts",
|
305
|
-
"#{project_name}/templates"
|
306
|
-
]
|
307
|
-
|
308
|
-
dirs.each do |dir|
|
309
|
-
FileUtils.mkdir_p(dir)
|
310
|
-
info "Created directory: #{dir}"
|
311
|
-
end
|
312
|
-
end
|
313
|
-
|
314
|
-
def create_sample_files(project_name)
|
315
|
-
create_deploy_script(project_name)
|
316
|
-
create_inventory_file(project_name)
|
317
|
-
create_config_file(project_name)
|
318
|
-
end
|
319
|
-
|
320
|
-
def create_deploy_script(project_name)
|
321
|
-
content = <<~RUBY
|
322
|
-
# frozen_string_literal: true
|
323
|
-
|
324
|
-
# Main deployment script
|
325
|
-
pipeline 'main' do
|
326
|
-
# Define target hosts
|
327
|
-
host 'app1.example.com', roles: [:app, :web]
|
328
|
-
host 'app2.example.com', roles: [:app, :web]
|
329
|
-
host 'db.example.com', roles: [:db]
|
330
|
-
|
331
|
-
# Set global variables
|
332
|
-
set :app_name, 'my_app'
|
333
|
-
set :deploy_to, '/var/www/${app_name}'
|
334
|
-
set :keep_releases, 5
|
335
|
-
|
336
|
-
# Define tasks
|
337
|
-
task :check_requirements do
|
338
|
-
run 'ruby -v'
|
339
|
-
run 'node -v'
|
340
|
-
run 'git --version'
|
341
|
-
end
|
342
|
-
|
343
|
-
task :setup_directories do
|
344
|
-
run "mkdir -p ${deploy_to}"
|
345
|
-
run "mkdir -p ${deploy_to}/releases"
|
346
|
-
run "mkdir -p ${deploy_to}/shared"
|
347
|
-
end
|
348
|
-
|
349
|
-
task :deploy do
|
350
|
-
depends_on :check_requirements, :setup_directories
|
351
|
-
|
352
|
-
run 'git clone https://github.com/user/repo.git ${deploy_to}/releases/$(date +%Y%m%d%H%M%S)'
|
353
|
-
run 'ln -sfn ${deploy_to}/releases/$(ls -t ${deploy_to}/releases | head -n1) ${deploy_to}/current'
|
354
|
-
end
|
355
|
-
|
356
|
-
task :restart_services do
|
357
|
-
run 'sudo systemctl restart nginx'
|
358
|
-
run 'sudo systemctl restart app'
|
359
|
-
end
|
360
|
-
|
361
|
-
task :cleanup do
|
362
|
-
run "cd ${deploy_to}/releases && ls -t | tail -n +${keep_releases + 1} | xargs rm -rf"
|
363
|
-
end
|
364
|
-
end
|
365
|
-
RUBY
|
366
|
-
|
367
|
-
write_file("#{project_name}/deploy.rb", content)
|
368
|
-
info "Created deployment script: #{project_name}/deploy.rb"
|
369
|
-
end
|
370
|
-
|
371
|
-
def create_inventory_file(project_name)
|
372
|
-
content = <<~YAML
|
373
|
-
# Server inventory configuration
|
374
|
-
groups:
|
375
|
-
production:
|
376
|
-
hosts:
|
377
|
-
- hostname: app1.example.com
|
378
|
-
roles: [app, web]
|
379
|
-
user: deploy
|
380
|
-
port: 22
|
381
|
-
- hostname: app2.example.com
|
382
|
-
roles: [app, web]
|
383
|
-
user: deploy
|
384
|
-
port: 22
|
385
|
-
- hostname: db.example.com
|
386
|
-
roles: [db]
|
387
|
-
user: deploy
|
388
|
-
port: 22
|
389
|
-
vars:
|
390
|
-
db_name: production_db
|
391
|
-
db_user: app_user
|
392
|
-
|
393
|
-
staging:
|
394
|
-
hosts:
|
395
|
-
- hostname: staging.example.com
|
396
|
-
roles: [app, web, db]
|
397
|
-
user: deploy
|
398
|
-
port: 22
|
399
|
-
vars:
|
400
|
-
rails_env: staging
|
401
|
-
node_env: staging
|
402
|
-
YAML
|
403
|
-
|
404
|
-
write_file("#{project_name}/inventory.yml", content)
|
405
|
-
info "Created inventory file: #{project_name}/inventory.yml"
|
406
|
-
end
|
407
|
-
|
408
|
-
def create_config_file(project_name)
|
409
|
-
content = <<~YAML
|
410
|
-
# Deployment configuration
|
411
|
-
max_concurrent_tasks: 5
|
412
|
-
retry_count: 3
|
413
|
-
retry_delay: 5
|
414
|
-
ssh_timeout: 30
|
415
|
-
command_timeout: 300
|
416
|
-
|
417
|
-
default_user: deploy
|
418
|
-
default_port: 22
|
419
|
-
|
420
|
-
template_dir: templates
|
421
|
-
inventory_file: inventory.yml
|
422
|
-
|
423
|
-
ssh_options:
|
424
|
-
forward_agent: true
|
425
|
-
verify_host_key: true
|
426
|
-
keepalive: true
|
427
|
-
keepalive_interval: 30
|
428
|
-
|
429
|
-
logging:
|
430
|
-
level: info
|
431
|
-
file: kdeploy.log
|
432
|
-
YAML
|
433
|
-
|
434
|
-
write_file("#{project_name}/config/kdeploy.yml", content)
|
435
|
-
info "Created configuration file: #{project_name}/config/kdeploy.yml"
|
436
|
-
end
|
437
|
-
|
438
|
-
def create_sample_scripts(project_name)
|
439
|
-
create_setup_script(project_name)
|
440
|
-
create_database_script(project_name)
|
441
|
-
create_backup_script(project_name)
|
442
|
-
create_monitoring_script(project_name)
|
443
|
-
create_rollback_script(project_name)
|
444
|
-
create_cleanup_script(project_name)
|
445
|
-
end
|
446
|
-
|
447
|
-
def create_setup_script(project_name)
|
448
|
-
content = <<~RUBY
|
449
|
-
# frozen_string_literal: true
|
450
|
-
|
451
|
-
# Server setup script
|
452
|
-
pipeline 'setup' do
|
453
|
-
# Target all hosts
|
454
|
-
host 'app1.example.com', roles: [:app, :web]
|
455
|
-
host 'app2.example.com', roles: [:app, :web]
|
456
|
-
host 'db.example.com', roles: [:db]
|
457
|
-
|
458
|
-
# Set global variables
|
459
|
-
set :ruby_version, '3.2.0'
|
460
|
-
set :node_version, '18.x'
|
461
|
-
|
462
|
-
task :install_dependencies do
|
463
|
-
run <<~BASH
|
464
|
-
# Update package lists
|
465
|
-
sudo apt-get update
|
466
|
-
|
467
|
-
# Install essential packages
|
468
|
-
sudo apt-get install -y build-essential git curl
|
469
|
-
sudo apt-get install -y nginx redis-server
|
470
|
-
BASH
|
471
|
-
end
|
472
|
-
|
473
|
-
task :setup_ruby do
|
474
|
-
run <<~BASH
|
475
|
-
# Install rbenv
|
476
|
-
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
|
477
|
-
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
|
478
|
-
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
|
479
|
-
source ~/.bashrc
|
480
|
-
|
481
|
-
# Install ruby-build
|
482
|
-
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
|
483
|
-
|
484
|
-
# Install Ruby
|
485
|
-
rbenv install ${ruby_version}
|
486
|
-
rbenv global ${ruby_version}
|
487
|
-
BASH
|
488
|
-
end
|
489
|
-
|
490
|
-
task :setup_node do
|
491
|
-
run <<~BASH
|
492
|
-
# Install Node.js
|
493
|
-
curl -fsSL https://deb.nodesource.com/setup_${node_version} | sudo -E bash -
|
494
|
-
sudo apt-get install -y nodejs
|
495
|
-
|
496
|
-
# Install Yarn
|
497
|
-
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
498
|
-
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
499
|
-
sudo apt-get update && sudo apt-get install -y yarn
|
500
|
-
BASH
|
501
|
-
end
|
502
|
-
|
503
|
-
task :setup_nginx do
|
504
|
-
# Upload nginx configuration
|
505
|
-
upload_template 'nginx.conf', '/etc/nginx/sites-available/app'
|
506
|
-
run 'sudo ln -sf /etc/nginx/sites-available/app /etc/nginx/sites-enabled/app'
|
507
|
-
run 'sudo nginx -t && sudo systemctl restart nginx'
|
508
|
-
end
|
509
|
-
|
510
|
-
task :setup_app_service do
|
511
|
-
# Upload systemd service configuration
|
512
|
-
upload_template 'app.service', '/etc/systemd/system/app.service'
|
513
|
-
run 'sudo systemctl daemon-reload'
|
514
|
-
run 'sudo systemctl enable app'
|
515
|
-
end
|
516
|
-
|
517
|
-
task :setup do
|
518
|
-
depends_on :install_dependencies,
|
519
|
-
:setup_ruby,
|
520
|
-
:setup_node,
|
521
|
-
:setup_nginx,
|
522
|
-
:setup_app_service
|
523
|
-
end
|
524
|
-
end
|
525
|
-
RUBY
|
526
|
-
|
527
|
-
write_file("#{project_name}/scripts/setup.rb", content)
|
528
|
-
info "Created setup script: #{project_name}/scripts/setup.rb"
|
529
|
-
end
|
530
|
-
|
531
|
-
def create_database_script(project_name)
|
532
|
-
content = <<~RUBY
|
533
|
-
# frozen_string_literal: true
|
534
|
-
|
535
|
-
# Database management script
|
536
|
-
pipeline 'database' do
|
537
|
-
# Target database hosts
|
538
|
-
host 'db.example.com', roles: [:db]
|
539
|
-
|
540
|
-
# Set database configuration
|
541
|
-
set :db_name, 'app_production'
|
542
|
-
set :db_user, 'app_user'
|
543
|
-
set :db_password, ENV['DB_PASSWORD']
|
544
|
-
|
545
|
-
task :create_database do
|
546
|
-
run <<~SQL
|
547
|
-
psql -U postgres -c "CREATE USER ${db_user} WITH PASSWORD '${db_password}';"
|
548
|
-
psql -U postgres -c "CREATE DATABASE ${db_name} OWNER ${db_user};"
|
549
|
-
SQL
|
550
|
-
end
|
551
|
-
|
552
|
-
task :migrate do
|
553
|
-
run 'cd /var/www/app/current && RAILS_ENV=production bundle exec rake db:migrate'
|
554
|
-
end
|
555
|
-
|
556
|
-
task :seed do
|
557
|
-
run 'cd /var/www/app/current && RAILS_ENV=production bundle exec rake db:seed'
|
558
|
-
end
|
559
|
-
|
560
|
-
task :backup do
|
561
|
-
run <<~BASH
|
562
|
-
timestamp=$(date +%Y%m%d_%H%M%S)
|
563
|
-
pg_dump -U ${db_user} ${db_name} > /var/backups/${db_name}_${timestamp}.sql
|
564
|
-
gzip /var/backups/${db_name}_${timestamp}.sql
|
565
|
-
BASH
|
566
|
-
end
|
567
|
-
|
568
|
-
task :restore do
|
569
|
-
run <<~BASH
|
570
|
-
latest_backup=$(ls -t /var/backups/${db_name}_*.sql.gz | head -n1)
|
571
|
-
gunzip -c $latest_backup | psql -U ${db_user} ${db_name}
|
572
|
-
BASH
|
573
|
-
end
|
574
|
-
end
|
575
|
-
RUBY
|
576
|
-
|
577
|
-
write_file("#{project_name}/scripts/database.rb", content)
|
578
|
-
info "Created database script: #{project_name}/scripts/database.rb"
|
579
|
-
end
|
580
|
-
|
581
|
-
def create_backup_script(project_name)
|
582
|
-
content = <<~RUBY
|
583
|
-
# frozen_string_literal: true
|
584
|
-
|
585
|
-
# Backup operations script
|
586
|
-
pipeline 'backup' do
|
587
|
-
# Target all hosts
|
588
|
-
host 'app1.example.com', roles: [:app, :web]
|
589
|
-
host 'app2.example.com', roles: [:app, :web]
|
590
|
-
host 'db.example.com', roles: [:db]
|
591
|
-
|
592
|
-
# Set backup configuration
|
593
|
-
set :backup_dir, '/var/backups'
|
594
|
-
set :keep_backups, 10
|
595
|
-
|
596
|
-
task :backup_database do
|
597
|
-
only :db
|
598
|
-
run <<~BASH
|
599
|
-
timestamp=$(date +%Y%m%d_%H%M%S)
|
600
|
-
pg_dump -U ${db_user} ${db_name} > ${backup_dir}/${db_name}_${timestamp}.sql
|
601
|
-
gzip ${backup_dir}/${db_name}_${timestamp}.sql
|
602
|
-
BASH
|
603
|
-
end
|
604
|
-
|
605
|
-
task :backup_uploads do
|
606
|
-
only [:app, :web]
|
607
|
-
run <<~BASH
|
608
|
-
timestamp=$(date +%Y%m%d_%H%M%S)
|
609
|
-
tar -czf ${backup_dir}/uploads_${timestamp}.tar.gz /var/www/app/shared/public/uploads
|
610
|
-
BASH
|
611
|
-
end
|
612
|
-
|
613
|
-
task :backup_logs do
|
614
|
-
run <<~BASH
|
615
|
-
timestamp=$(date +%Y%m%d_%H%M%S)
|
616
|
-
tar -czf ${backup_dir}/logs_${timestamp}.tar.gz /var/log/app
|
617
|
-
BASH
|
618
|
-
end
|
619
|
-
|
620
|
-
task :cleanup_old_backups do
|
621
|
-
run <<~BASH
|
622
|
-
cd ${backup_dir}
|
623
|
-
ls -t *.sql.gz | tail -n +${keep_backups + 1} | xargs rm -f
|
624
|
-
ls -t *.tar.gz | tail -n +${keep_backups + 1} | xargs rm -f
|
625
|
-
BASH
|
626
|
-
end
|
627
|
-
|
628
|
-
task :backup do
|
629
|
-
depends_on :backup_database,
|
630
|
-
:backup_uploads,
|
631
|
-
:backup_logs,
|
632
|
-
:cleanup_old_backups
|
633
|
-
end
|
634
|
-
end
|
635
|
-
RUBY
|
636
|
-
|
637
|
-
write_file("#{project_name}/scripts/backup.rb", content)
|
638
|
-
info "Created backup script: #{project_name}/scripts/backup.rb"
|
68
|
+
desc 'init [DIR]', 'Initialize a new deployment project'
|
69
|
+
def init(dir = '.')
|
70
|
+
initializer = Initializer.new(dir)
|
71
|
+
initializer.run
|
72
|
+
rescue StandardError => e
|
73
|
+
puts Kdeploy::Banner.show_error(e.message)
|
74
|
+
exit 1
|
639
75
|
end
|
640
76
|
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
# Target all hosts
|
648
|
-
host 'app1.example.com', roles: [:app, :web]
|
649
|
-
host 'app2.example.com', roles: [:app, :web]
|
650
|
-
host 'db.example.com', roles: [:db]
|
651
|
-
|
652
|
-
task :check_system_resources do
|
653
|
-
run <<~BASH
|
654
|
-
echo "Memory Usage:"
|
655
|
-
free -h
|
656
|
-
echo "\\nDisk Usage:"
|
657
|
-
df -h
|
658
|
-
echo "\\nCPU Load:"
|
659
|
-
uptime
|
660
|
-
BASH
|
661
|
-
end
|
662
|
-
|
663
|
-
task :check_services do
|
664
|
-
run <<~BASH
|
665
|
-
echo "Nginx Status:"
|
666
|
-
sudo systemctl status nginx
|
667
|
-
echo "\\nApp Status:"
|
668
|
-
sudo systemctl status app
|
669
|
-
echo "\\nRedis Status:"
|
670
|
-
sudo systemctl status redis-server
|
671
|
-
BASH
|
672
|
-
end
|
77
|
+
desc 'execute TASK_FILE [TASK]', 'Execute deployment tasks from file'
|
78
|
+
method_option :limit, type: :string, desc: 'Limit to specific hosts (comma-separated)'
|
79
|
+
method_option :parallel, type: :numeric, default: 5, desc: 'Number of parallel executions'
|
80
|
+
method_option :dry_run, type: :boolean, desc: 'Show what would be done'
|
81
|
+
def execute(task_file, task_name = nil)
|
82
|
+
load_task_file(task_file)
|
673
83
|
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
sudo tail -n 50 /var/log/nginx/error.log
|
680
|
-
BASH
|
681
|
-
end
|
84
|
+
tasks_to_run = if task_name
|
85
|
+
[task_name.to_sym]
|
86
|
+
else
|
87
|
+
self.class.kdeploy_tasks.keys
|
88
|
+
end
|
682
89
|
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
echo "PostgreSQL Status:"
|
687
|
-
sudo systemctl status postgresql
|
688
|
-
echo "\\nDatabase Size:"
|
689
|
-
psql -U ${db_user} -d ${db_name} -c "\\l+"
|
690
|
-
BASH
|
691
|
-
end
|
90
|
+
tasks_to_run.each do |task|
|
91
|
+
task_hosts = self.class.get_task_hosts(task)
|
92
|
+
hosts = filter_hosts(options[:limit], task_hosts)
|
692
93
|
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
:check_logs,
|
697
|
-
:check_database
|
698
|
-
end
|
94
|
+
if hosts.empty?
|
95
|
+
puts Kdeploy::Banner.show_error("No hosts found for task: #{task}")
|
96
|
+
next
|
699
97
|
end
|
700
|
-
RUBY
|
701
|
-
|
702
|
-
write_file("#{project_name}/scripts/monitoring.rb", content)
|
703
|
-
info "Created monitoring script: #{project_name}/scripts/monitoring.rb"
|
704
|
-
end
|
705
|
-
|
706
|
-
def create_rollback_script(project_name)
|
707
|
-
content = <<~RUBY
|
708
|
-
# frozen_string_literal: true
|
709
98
|
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
host 'app1.example.com', roles: [:app, :web]
|
714
|
-
host 'app2.example.com', roles: [:app, :web]
|
715
|
-
|
716
|
-
# Set deployment configuration
|
717
|
-
set :app_name, 'my_app'
|
718
|
-
set :deploy_to, '/var/www/${app_name}'
|
719
|
-
|
720
|
-
task :list_releases do
|
721
|
-
run "ls -lt ${deploy_to}/releases"
|
722
|
-
end
|
723
|
-
|
724
|
-
task :rollback_code do
|
725
|
-
run <<~BASH
|
726
|
-
current_release=$(readlink ${deploy_to}/current)
|
727
|
-
previous_release=$(ls -t ${deploy_to}/releases | head -n 2 | tail -n 1)
|
728
|
-
ln -sfn ${deploy_to}/releases/$previous_release ${deploy_to}/current
|
729
|
-
BASH
|
730
|
-
end
|
731
|
-
|
732
|
-
task :rollback_database do
|
733
|
-
run 'cd ${deploy_to}/current && RAILS_ENV=production bundle exec rake db:rollback STEP=1'
|
734
|
-
end
|
735
|
-
|
736
|
-
task :restart_services do
|
737
|
-
run 'sudo systemctl restart app'
|
738
|
-
run 'sudo systemctl restart nginx'
|
739
|
-
end
|
740
|
-
|
741
|
-
task :rollback do
|
742
|
-
depends_on :list_releases,
|
743
|
-
:rollback_code,
|
744
|
-
:rollback_database,
|
745
|
-
:restart_services
|
746
|
-
end
|
99
|
+
if options[:dry_run]
|
100
|
+
print_dry_run(hosts, task)
|
101
|
+
next
|
747
102
|
end
|
748
|
-
RUBY
|
749
|
-
|
750
|
-
write_file("#{project_name}/scripts/rollback.rb", content)
|
751
|
-
info "Created rollback script: #{project_name}/scripts/rollback.rb"
|
752
|
-
end
|
753
|
-
|
754
|
-
def create_cleanup_script(project_name)
|
755
|
-
content = <<~RUBY
|
756
|
-
# frozen_string_literal: true
|
757
|
-
|
758
|
-
# Cleanup operations script
|
759
|
-
pipeline 'cleanup' do
|
760
|
-
# Target all hosts
|
761
|
-
host 'app1.example.com', roles: [:app, :web]
|
762
|
-
host 'app2.example.com', roles: [:app, :web]
|
763
|
-
host 'db.example.com', roles: [:db]
|
764
|
-
|
765
|
-
# Set cleanup configuration
|
766
|
-
set :app_name, 'my_app'
|
767
|
-
set :deploy_to, '/var/www/${app_name}'
|
768
|
-
set :keep_releases, 5
|
769
|
-
set :keep_logs, 7
|
770
|
-
|
771
|
-
task :cleanup_releases do
|
772
|
-
run <<~BASH
|
773
|
-
cd ${deploy_to}/releases
|
774
|
-
ls -t | tail -n +${keep_releases + 1} | xargs rm -rf
|
775
|
-
BASH
|
776
|
-
end
|
777
103
|
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
find /var/log/nginx -name "*.log.*" -mtime +${keep_logs} -exec sudo rm {} \\;
|
782
|
-
BASH
|
783
|
-
end
|
784
|
-
|
785
|
-
task :cleanup_temp do
|
786
|
-
run <<~BASH
|
787
|
-
find /tmp -name "#{app_name}-*" -mtime +1 -exec rm -rf {} \\;
|
788
|
-
find /var/tmp -name "#{app_name}-*" -mtime +1 -exec rm -rf {} \\;
|
789
|
-
BASH
|
790
|
-
end
|
791
|
-
|
792
|
-
task :cleanup do
|
793
|
-
depends_on :cleanup_releases,
|
794
|
-
:cleanup_logs,
|
795
|
-
:cleanup_temp
|
796
|
-
end
|
797
|
-
end
|
798
|
-
RUBY
|
799
|
-
|
800
|
-
write_file("#{project_name}/scripts/cleanup.rb", content)
|
801
|
-
info "Created cleanup script: #{project_name}/scripts/cleanup.rb"
|
802
|
-
end
|
803
|
-
|
804
|
-
def create_sample_templates(project_name)
|
805
|
-
create_nginx_template(project_name)
|
806
|
-
create_app_service_template(project_name)
|
807
|
-
create_deploy_script_template(project_name)
|
808
|
-
create_backup_script_template(project_name)
|
809
|
-
end
|
810
|
-
|
811
|
-
def create_nginx_template(project_name)
|
812
|
-
content = <<~ERB
|
813
|
-
# Nginx configuration for <%= app_name %>
|
814
|
-
upstream app_server {
|
815
|
-
server unix:/var/www/<%= app_name %>/shared/tmp/sockets/puma.sock fail_timeout=0;
|
816
|
-
}
|
817
|
-
|
818
|
-
server {
|
819
|
-
listen 80;
|
820
|
-
server_name <%= server_name %>;
|
821
|
-
root /var/www/<%= app_name %>/current/public;
|
822
|
-
|
823
|
-
location ^~ /assets/ {
|
824
|
-
gzip_static on;
|
825
|
-
expires max;
|
826
|
-
add_header Cache-Control public;
|
827
|
-
}
|
828
|
-
|
829
|
-
try_files $uri/index.html $uri @app;
|
830
|
-
|
831
|
-
location @app {
|
832
|
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
833
|
-
proxy_set_header Host $http_host;
|
834
|
-
proxy_redirect off;
|
835
|
-
proxy_pass http://app_server;
|
836
|
-
}
|
837
|
-
|
838
|
-
error_page 500 502 503 504 /500.html;
|
839
|
-
client_max_body_size 4G;
|
840
|
-
keepalive_timeout 10;
|
841
|
-
}
|
842
|
-
ERB
|
843
|
-
|
844
|
-
write_file("#{project_name}/templates/nginx.conf.erb", content)
|
845
|
-
info "Created nginx template: #{project_name}/templates/nginx.conf.erb"
|
846
|
-
end
|
847
|
-
|
848
|
-
def create_app_service_template(project_name)
|
849
|
-
content = <<~ERB
|
850
|
-
[Unit]
|
851
|
-
Description=<%= app_name %> application server
|
852
|
-
After=network.target
|
853
|
-
|
854
|
-
[Service]
|
855
|
-
Type=simple
|
856
|
-
User=<%= app_user %>
|
857
|
-
WorkingDirectory=/var/www/<%= app_name %>/current
|
858
|
-
Environment=RAILS_ENV=production
|
859
|
-
Environment=PATH=/home/<%= app_user %>/.rbenv/shims:/usr/local/bin:/usr/bin:/bin
|
860
|
-
ExecStart=/home/<%= app_user %>/.rbenv/shims/bundle exec puma -C config/puma.rb
|
861
|
-
Restart=always
|
862
|
-
RestartSec=1
|
863
|
-
|
864
|
-
[Install]
|
865
|
-
WantedBy=multi-user.target
|
866
|
-
ERB
|
867
|
-
|
868
|
-
write_file("#{project_name}/templates/app.service.erb", content)
|
869
|
-
info "Created app service template: #{project_name}/templates/app.service.erb"
|
870
|
-
end
|
871
|
-
|
872
|
-
def create_deploy_script_template(project_name)
|
873
|
-
content = <<~ERB
|
874
|
-
#!/bin/bash
|
875
|
-
# Deployment script for <%= app_name %>
|
876
|
-
|
877
|
-
set -e
|
878
|
-
|
879
|
-
APP_ROOT="/var/www/<%= app_name %>"
|
880
|
-
CURRENT="$APP_ROOT/current"
|
881
|
-
SHARED="$APP_ROOT/shared"
|
882
|
-
RELEASE="$APP_ROOT/releases/$(date +%Y%m%d%H%M%S)"
|
883
|
-
|
884
|
-
echo "Deploying <%= app_name %> to $RELEASE"
|
885
|
-
|
886
|
-
# Create release directory
|
887
|
-
mkdir -p $RELEASE
|
888
|
-
|
889
|
-
# Clone repository
|
890
|
-
git clone <%= repository_url %> $RELEASE
|
891
|
-
|
892
|
-
# Install dependencies
|
893
|
-
cd $RELEASE
|
894
|
-
bundle install --deployment --without development test
|
895
|
-
yarn install --production
|
896
|
-
|
897
|
-
# Compile assets
|
898
|
-
bundle exec rake assets:precompile RAILS_ENV=production
|
899
|
-
|
900
|
-
# Create symlinks
|
901
|
-
ln -s $SHARED/config/database.yml $RELEASE/config/database.yml
|
902
|
-
ln -s $SHARED/config/master.key $RELEASE/config/master.key
|
903
|
-
ln -s $SHARED/public/uploads $RELEASE/public/uploads
|
904
|
-
|
905
|
-
# Update current symlink
|
906
|
-
ln -sfn $RELEASE $CURRENT
|
907
|
-
|
908
|
-
# Restart application
|
909
|
-
sudo systemctl restart <%= app_name %>
|
910
|
-
|
911
|
-
echo "Deployment completed successfully!"
|
912
|
-
ERB
|
913
|
-
|
914
|
-
write_file("#{project_name}/templates/deploy.sh.erb", content)
|
915
|
-
info "Created deploy script template: #{project_name}/templates/deploy.sh.erb"
|
916
|
-
end
|
917
|
-
|
918
|
-
def create_backup_script_template(project_name)
|
919
|
-
content = <<~ERB
|
920
|
-
#!/bin/bash
|
921
|
-
# Backup script for <%= app_name %>
|
922
|
-
|
923
|
-
set -e
|
924
|
-
|
925
|
-
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
926
|
-
BACKUP_DIR="<%= backup_dir %>"
|
927
|
-
DB_NAME="<%= db_name %>"
|
928
|
-
APP_ROOT="/var/www/<%= app_name %>"
|
929
|
-
|
930
|
-
echo "Starting backup for <%= app_name %>"
|
931
|
-
|
932
|
-
# Create backup directory
|
933
|
-
mkdir -p $BACKUP_DIR
|
934
|
-
|
935
|
-
# Backup database
|
936
|
-
echo "Backing up database..."
|
937
|
-
pg_dump -U <%= db_user %> $DB_NAME > $BACKUP_DIR/${DB_NAME}_${TIMESTAMP}.sql
|
938
|
-
gzip $BACKUP_DIR/${DB_NAME}_${TIMESTAMP}.sql
|
939
|
-
|
940
|
-
# Backup uploads
|
941
|
-
echo "Backing up uploads..."
|
942
|
-
tar -czf $BACKUP_DIR/uploads_${TIMESTAMP}.tar.gz $APP_ROOT/shared/public/uploads
|
943
|
-
|
944
|
-
# Backup configuration
|
945
|
-
echo "Backing up configuration..."
|
946
|
-
tar -czf $BACKUP_DIR/config_${TIMESTAMP}.tar.gz $APP_ROOT/shared/config
|
947
|
-
|
948
|
-
# Cleanup old backups
|
949
|
-
echo "Cleaning up old backups..."
|
950
|
-
find $BACKUP_DIR -name "*.sql.gz" -mtime +<%= keep_days %> -delete
|
951
|
-
find $BACKUP_DIR -name "*.tar.gz" -mtime +<%= keep_days %> -delete
|
952
|
-
|
953
|
-
echo "Backup completed successfully!"
|
954
|
-
ERB
|
955
|
-
|
956
|
-
write_file("#{project_name}/templates/backup.sh.erb", content)
|
957
|
-
info "Created backup script template: #{project_name}/templates/backup.sh.erb"
|
958
|
-
end
|
959
|
-
|
960
|
-
def write_file(path, content)
|
961
|
-
File.write(path, content)
|
962
|
-
end
|
963
|
-
|
964
|
-
def determine_project_name(project_name)
|
965
|
-
project_name || options[:name] || File.basename(Dir.pwd)
|
966
|
-
end
|
967
|
-
|
968
|
-
def display_init_header(project_name)
|
969
|
-
puts ''
|
970
|
-
puts "š Initializing kdeploy project: #{project_name}".colorize(:yellow)
|
971
|
-
puts ''
|
972
|
-
end
|
973
|
-
|
974
|
-
def display_init_success(project_name)
|
975
|
-
puts ''
|
976
|
-
puts 'ā
Project initialized successfully!'.colorize(:green)
|
977
|
-
puts ''
|
978
|
-
display_project_structure(project_name)
|
979
|
-
display_next_steps(project_name)
|
980
|
-
display_available_scripts
|
981
|
-
end
|
982
|
-
|
983
|
-
def display_project_structure(project_name)
|
984
|
-
puts 'š Project Structure Created:'.colorize(:cyan)
|
985
|
-
puts " #{project_name}/".colorize(:light_blue)
|
986
|
-
puts ' āāā deploy.rb # Main deployment script'.colorize(:light_blue)
|
987
|
-
puts ' āāā inventory.yml # Server inventory'.colorize(:light_blue)
|
988
|
-
puts ' āāā config/ # Configuration files'.colorize(:light_blue)
|
989
|
-
puts ' ā āāā kdeploy.yml # Deployment configuration'.colorize(:light_blue)
|
990
|
-
puts ' āāā scripts/ # Additional scripts'.colorize(:light_blue)
|
991
|
-
puts ' ā āāā setup.rb # Server setup script'.colorize(:light_blue)
|
992
|
-
puts ' ā āāā database.rb # Database management'.colorize(:light_blue)
|
993
|
-
puts ' ā āāā backup.rb # Backup operations'.colorize(:light_blue)
|
994
|
-
puts ' ā āāā monitoring.rb # Health checks & monitoring'.colorize(:light_blue)
|
995
|
-
puts ' ā āāā rollback.rb # Rollback operations'.colorize(:light_blue)
|
996
|
-
puts ' ā āāā cleanup.rb # Cleanup operations'.colorize(:light_blue)
|
997
|
-
puts ' āāā templates/ # Configuration templates'.colorize(:light_blue)
|
998
|
-
puts ' āāā nginx.conf.erb # Nginx configuration'.colorize(:light_blue)
|
999
|
-
puts ' āāā app.service.erb # Systemd service'.colorize(:light_blue)
|
1000
|
-
puts ' āāā deploy.sh.erb # Deployment script'.colorize(:light_blue)
|
1001
|
-
puts ' āāā backup.sh.erb # Backup script'.colorize(:light_blue)
|
1002
|
-
puts ''
|
1003
|
-
end
|
1004
|
-
|
1005
|
-
def display_next_steps(project_name)
|
1006
|
-
puts 'š Next Steps:'.colorize(:cyan)
|
1007
|
-
puts " 1. cd #{project_name}".colorize(:light_blue)
|
1008
|
-
puts ' 2. Edit deploy.rb to configure your deployment'.colorize(:light_blue)
|
1009
|
-
puts ' 3. Update inventory.yml with your servers'.colorize(:light_blue)
|
1010
|
-
puts ' 4. Run: kdeploy deploy scripts/setup.rb # Setup servers'.colorize(:light_blue)
|
1011
|
-
puts ' 5. Run: kdeploy deploy deploy.rb # Deploy application'.colorize(:light_blue)
|
1012
|
-
puts ''
|
1013
|
-
end
|
1014
|
-
|
1015
|
-
def display_available_scripts
|
1016
|
-
puts 'š” Available Scripts:'.colorize(:cyan)
|
1017
|
-
puts ' kdeploy deploy scripts/setup.rb # Initial server setup'.colorize(:light_blue)
|
1018
|
-
puts ' kdeploy deploy scripts/database.rb # Database operations'.colorize(:light_blue)
|
1019
|
-
puts ' kdeploy deploy scripts/backup.rb # Backup operations'.colorize(:light_blue)
|
1020
|
-
puts ' kdeploy deploy scripts/monitoring.rb # Health checks'.colorize(:light_blue)
|
1021
|
-
puts ' kdeploy deploy scripts/rollback.rb # Rollback operations'.colorize(:light_blue)
|
1022
|
-
puts ' kdeploy deploy scripts/cleanup.rb # Cleanup operations'.colorize(:light_blue)
|
1023
|
-
puts ''
|
1024
|
-
puts 'š” Need help? Run: kdeploy help deploy'.colorize(:yellow)
|
1025
|
-
puts ''
|
1026
|
-
end
|
1027
|
-
|
1028
|
-
def validate_script_file(script_file)
|
1029
|
-
return if File.exist?(script_file)
|
1030
|
-
|
1031
|
-
error "Script file not found: #{script_file}"
|
1032
|
-
exit 1
|
1033
|
-
end
|
1034
|
-
|
1035
|
-
def display_validation_header(script_file)
|
1036
|
-
puts ''
|
1037
|
-
puts "š Validating deployment script: #{script_file}".colorize(:yellow)
|
1038
|
-
puts ''
|
1039
|
-
end
|
1040
|
-
|
1041
|
-
def display_validation_success(pipeline)
|
1042
|
-
puts 'ā
Script validation passed'.colorize(:green)
|
1043
|
-
puts ''
|
1044
|
-
display_pipeline_summary(pipeline)
|
1045
|
-
display_target_hosts(pipeline.hosts) if pipeline.hosts.any?
|
1046
|
-
display_tasks(pipeline.tasks) if pipeline.tasks.any?
|
1047
|
-
puts ''
|
1048
|
-
end
|
1049
|
-
|
1050
|
-
def display_pipeline_summary(pipeline)
|
1051
|
-
puts 'š Pipeline Summary:'.colorize(:cyan)
|
1052
|
-
puts " Name: #{pipeline.name}".colorize(:light_blue)
|
1053
|
-
puts " Hosts: #{pipeline.hosts.size}".colorize(:light_blue)
|
1054
|
-
puts " Tasks: #{pipeline.tasks.size}".colorize(:light_blue)
|
1055
|
-
end
|
1056
|
-
|
1057
|
-
def display_target_hosts(hosts)
|
1058
|
-
puts ''
|
1059
|
-
puts 'š„ļø Target Hosts:'.colorize(:cyan)
|
1060
|
-
hosts.each do |host|
|
1061
|
-
puts " ⢠#{host.hostname}:#{host.port} (#{host.user})".colorize(:light_blue)
|
1062
|
-
end
|
1063
|
-
end
|
1064
|
-
|
1065
|
-
def display_tasks(tasks)
|
1066
|
-
puts ''
|
1067
|
-
puts 'š§ Tasks to Execute:'.colorize(:cyan)
|
1068
|
-
tasks.each_with_index do |task, index|
|
1069
|
-
puts " #{index + 1}. #{task.name}".colorize(:light_blue)
|
104
|
+
runner = Runner.new(hosts, self.class.kdeploy_tasks, parallel: options[:parallel])
|
105
|
+
results = runner.run(task)
|
106
|
+
print_results(results)
|
1070
107
|
end
|
1071
|
-
|
1072
|
-
|
1073
|
-
def display_validation_errors(errors)
|
1074
|
-
puts 'ā Script validation failed:'.colorize(:red)
|
1075
|
-
puts ''
|
1076
|
-
errors.each { |err| puts " ⢠#{err}".colorize(:red) }
|
1077
|
-
puts ''
|
1078
|
-
exit 1
|
1079
|
-
end
|
1080
|
-
|
1081
|
-
def display_validation_failure(error)
|
1082
|
-
puts "ā Validation failed: #{error.message}".colorize(:red)
|
1083
|
-
puts ''
|
1084
|
-
exit 1
|
1085
|
-
end
|
1086
|
-
|
1087
|
-
def handle_deployment_error(error)
|
1088
|
-
error "Deployment failed: #{error.message}"
|
1089
|
-
exit 1
|
1090
|
-
end
|
1091
|
-
|
1092
|
-
def handle_unexpected_error(error)
|
1093
|
-
error "Unexpected error: #{error.message}"
|
1094
|
-
KdeployLogger.debug("Backtrace: #{error.backtrace.join("\n")}")
|
108
|
+
rescue StandardError => e
|
109
|
+
puts Kdeploy::Banner.show_error(e.message)
|
1095
110
|
exit 1
|
1096
111
|
end
|
1097
112
|
|
1098
|
-
|
1099
|
-
show_kdeploy_banner
|
1100
|
-
stats = Kdeploy.statistics
|
1101
|
-
deployment_summary = stats.deployment_summary(days: options[:days])
|
1102
|
-
task_summary = stats.task_summary(days: options[:days])
|
1103
|
-
global_summary = stats.global_summary
|
1104
|
-
|
1105
|
-
case options[:format].downcase
|
1106
|
-
when 'json'
|
1107
|
-
display_json_summary(deployment_summary, task_summary, global_summary)
|
1108
|
-
else
|
1109
|
-
print_summary_table(deployment_summary, task_summary, global_summary)
|
1110
|
-
end
|
1111
|
-
end
|
1112
|
-
|
1113
|
-
def show_deployment_stats
|
1114
|
-
show_kdeploy_banner
|
1115
|
-
stats = Kdeploy.statistics
|
1116
|
-
summary = stats.deployment_summary(days: options[:days])
|
1117
|
-
|
1118
|
-
case options[:format].downcase
|
1119
|
-
when 'json'
|
1120
|
-
puts JSON.pretty_generate(summary)
|
1121
|
-
else
|
1122
|
-
print_deployment_table(summary)
|
1123
|
-
end
|
1124
|
-
end
|
1125
|
-
|
1126
|
-
def show_task_stats
|
1127
|
-
show_kdeploy_banner
|
1128
|
-
stats = Kdeploy.statistics
|
1129
|
-
summary = stats.task_summary(days: options[:days])
|
1130
|
-
|
1131
|
-
case options[:format].downcase
|
1132
|
-
when 'json'
|
1133
|
-
puts JSON.pretty_generate(summary)
|
1134
|
-
else
|
1135
|
-
print_task_table(summary)
|
1136
|
-
end
|
1137
|
-
end
|
1138
|
-
|
1139
|
-
def show_failure_stats
|
1140
|
-
show_kdeploy_banner
|
1141
|
-
stats = Kdeploy.statistics
|
1142
|
-
failed_tasks = stats.top_failed_tasks(limit: 10, days: options[:days])
|
1143
|
-
|
1144
|
-
case options[:format].downcase
|
1145
|
-
when 'json'
|
1146
|
-
puts JSON.pretty_generate(failed_tasks)
|
1147
|
-
else
|
1148
|
-
print_failure_table(failed_tasks)
|
1149
|
-
end
|
1150
|
-
end
|
1151
|
-
|
1152
|
-
def show_trend_stats
|
1153
|
-
show_kdeploy_banner
|
1154
|
-
stats = Kdeploy.statistics
|
1155
|
-
trends = stats.performance_trends(days: options[:days])
|
1156
|
-
|
1157
|
-
case options[:format].downcase
|
1158
|
-
when 'json'
|
1159
|
-
puts JSON.pretty_generate(trends)
|
1160
|
-
else
|
1161
|
-
print_trend_table(trends)
|
1162
|
-
end
|
1163
|
-
end
|
1164
|
-
|
1165
|
-
def show_global_stats
|
1166
|
-
show_kdeploy_banner
|
1167
|
-
stats = Kdeploy.statistics
|
1168
|
-
global_summary = stats.global_summary
|
113
|
+
private
|
1169
114
|
|
1170
|
-
|
1171
|
-
|
1172
|
-
puts
|
1173
|
-
|
1174
|
-
print_global_table(global_summary)
|
115
|
+
def load_task_file(file)
|
116
|
+
unless File.exist?(file)
|
117
|
+
puts Kdeploy::Banner.show_error("Task file not found: #{file}")
|
118
|
+
exit 1
|
1175
119
|
end
|
1176
|
-
end
|
1177
120
|
|
1178
|
-
|
1179
|
-
show_kdeploy_banner
|
1180
|
-
prompt = TTY::Prompt.new
|
1181
|
-
if prompt.yes?('Are you sure you want to clear all statistics? This cannot be undone.')
|
1182
|
-
Kdeploy.statistics.clear_statistics!
|
1183
|
-
success 'Statistics cleared successfully'
|
1184
|
-
else
|
1185
|
-
info 'Statistics clearing cancelled'
|
1186
|
-
end
|
121
|
+
self.class.class_eval(File.read(file), file)
|
1187
122
|
end
|
1188
123
|
|
1189
|
-
def
|
1190
|
-
|
1191
|
-
|
1192
|
-
format = determine_export_format(export_file)
|
124
|
+
def filter_hosts(limit, task_hosts)
|
125
|
+
hosts = self.class.kdeploy_hosts.select { |name, _| task_hosts.include?(name) }
|
126
|
+
return hosts unless limit
|
1193
127
|
|
1194
|
-
|
1195
|
-
|
128
|
+
host_names = limit.split(',').map(&:strip)
|
129
|
+
hosts.select { |name, _| host_names.include?(name) }
|
1196
130
|
end
|
1197
131
|
|
1198
|
-
def
|
1199
|
-
puts
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
132
|
+
def print_dry_run(hosts, task_name)
|
133
|
+
puts Kdeploy::Banner.show
|
134
|
+
pastel = Pastel.new
|
135
|
+
puts TTY::Box.frame(
|
136
|
+
'Showing what would be done without executing any commands',
|
137
|
+
title: { top_left: ' Dry Run Mode ', bottom_right: ' Kdeploy ' },
|
138
|
+
style: {
|
139
|
+
border: {
|
140
|
+
fg: :blue
|
141
|
+
}
|
1204
142
|
}
|
1205
143
|
)
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
def print_task_section(summary)
|
1231
|
-
puts "\nš§ Task Statistics".colorize(:yellow)
|
1232
|
-
if summary[:total_task_executions].positive?
|
1233
|
-
puts " Total Task Executions: #{summary[:total_task_executions]}"
|
1234
|
-
puts " Unique Tasks: #{summary[:unique_tasks]}"
|
1235
|
-
print_top_tasks(summary[:tasks]) if summary[:tasks].any?
|
1236
|
-
else
|
1237
|
-
puts " No task executions in the last #{options[:days]} days"
|
1238
|
-
end
|
1239
|
-
end
|
1240
|
-
|
1241
|
-
def print_top_tasks(tasks)
|
1242
|
-
puts ' Top Tasks:'
|
1243
|
-
sorted_tasks = tasks.sort_by { |_, stats| -stats[:total_executions] }.first(5)
|
1244
|
-
sorted_tasks.each do |name, stats|
|
1245
|
-
puts " #{name}: #{stats[:total_executions]} executions (#{stats[:success_rate]}% success)"
|
1246
|
-
end
|
1247
|
-
end
|
1248
|
-
|
1249
|
-
def print_global_section(summary)
|
1250
|
-
puts "\nš Global Statistics".colorize(:yellow)
|
1251
|
-
puts " Total Deployments: #{summary[:total_deployments]}"
|
1252
|
-
puts " Total Tasks: #{summary[:total_tasks]}"
|
1253
|
-
puts " Total Commands: #{summary[:total_commands]}"
|
1254
|
-
puts " Total Execution Time: #{format_duration(summary[:total_execution_time])}"
|
1255
|
-
puts " Session Duration: #{format_duration(summary[:session_duration])}"
|
1256
|
-
puts ''
|
1257
|
-
end
|
1258
|
-
|
1259
|
-
def print_deployment_table(summary)
|
1260
|
-
puts "\nš¦ Deployment Statistics (Last #{options[:days]} days)".colorize(:cyan)
|
1261
|
-
puts '=' * 60
|
1262
|
-
|
1263
|
-
if summary[:total_deployments].zero?
|
1264
|
-
puts 'No deployments found'
|
1265
|
-
return
|
1266
|
-
end
|
1267
|
-
|
1268
|
-
display_deployment_stats(summary)
|
1269
|
-
end
|
1270
|
-
|
1271
|
-
def display_deployment_stats(summary)
|
1272
|
-
puts "Total Deployments: #{summary[:total_deployments]}"
|
1273
|
-
puts "Successful: #{summary[:successful_deployments]} (#{summary[:success_rate]}%)"
|
1274
|
-
puts "Failed: #{summary[:failed_deployments]}"
|
1275
|
-
puts "Average Duration: #{summary[:avg_duration]}s"
|
1276
|
-
puts "Min Duration: #{summary[:min_duration]}s"
|
1277
|
-
puts "Max Duration: #{summary[:max_duration]}s"
|
1278
|
-
puts "Total Duration: #{format_duration(summary[:total_duration])}"
|
1279
|
-
puts ''
|
1280
|
-
end
|
1281
|
-
|
1282
|
-
def print_task_table(summary)
|
1283
|
-
puts "\nš§ Task Statistics (Last #{options[:days]} days)".colorize(:cyan)
|
1284
|
-
puts '=' * 60
|
1285
|
-
|
1286
|
-
if summary[:total_task_executions].zero?
|
1287
|
-
puts 'No task executions found'
|
1288
|
-
return
|
1289
|
-
end
|
1290
|
-
|
1291
|
-
display_task_summary(summary)
|
1292
|
-
display_task_details(summary[:tasks]) if summary[:tasks].any?
|
1293
|
-
end
|
1294
|
-
|
1295
|
-
def display_task_summary(summary)
|
1296
|
-
puts "Total Executions: #{summary[:total_task_executions]}"
|
1297
|
-
puts "Unique Tasks: #{summary[:unique_tasks]}"
|
1298
|
-
puts ''
|
1299
|
-
end
|
1300
|
-
|
1301
|
-
def display_task_details(tasks)
|
1302
|
-
puts 'Task Details:'
|
1303
|
-
print_task_header
|
1304
|
-
tasks.each { |name, stats| print_task_row(name, stats) }
|
1305
|
-
puts ''
|
1306
|
-
end
|
1307
|
-
|
1308
|
-
def print_task_header
|
1309
|
-
printf "%-30s %10s %10s %10s %12s %12s\n",
|
1310
|
-
'Task Name', 'Executions', 'Success', 'Failed', 'Success Rate', 'Avg Duration'
|
1311
|
-
puts '-' * 95
|
1312
|
-
end
|
1313
|
-
|
1314
|
-
def print_task_row(name, stats)
|
1315
|
-
printf "%-30s %10d %10d %10d %11.1f%% %11.2fs\n",
|
1316
|
-
name.truncate(28),
|
1317
|
-
stats[:total_executions],
|
1318
|
-
stats[:successful],
|
1319
|
-
stats[:failed],
|
1320
|
-
stats[:success_rate],
|
1321
|
-
stats[:avg_duration]
|
1322
|
-
end
|
1323
|
-
|
1324
|
-
def print_failure_table(failed_tasks)
|
1325
|
-
puts "\nā Top Failed Tasks (Last #{options[:days]} days)".colorize(:red)
|
1326
|
-
puts '=' * 60
|
1327
|
-
|
1328
|
-
if failed_tasks.empty?
|
1329
|
-
puts 'No failed tasks found'
|
1330
|
-
return
|
1331
|
-
end
|
1332
|
-
|
1333
|
-
print_failure_header
|
1334
|
-
failed_tasks.each { |task| print_failure_row(task) }
|
1335
|
-
puts ''
|
1336
|
-
end
|
1337
|
-
|
1338
|
-
def print_failure_header
|
1339
|
-
printf "%-30s %10s %20s\n", 'Task Name', 'Failures', 'Last Failure'
|
1340
|
-
puts '-' * 62
|
1341
|
-
end
|
1342
|
-
|
1343
|
-
def print_failure_row(task)
|
1344
|
-
last_failure_time = Time.at(task[:last_failure][:timestamp]).strftime('%Y-%m-%d %H:%M:%S')
|
1345
|
-
printf "%-30s %10d %20s\n",
|
1346
|
-
task[:task_name].truncate(28),
|
1347
|
-
task[:failure_count],
|
1348
|
-
last_failure_time
|
1349
|
-
end
|
1350
|
-
|
1351
|
-
def print_trend_table(trends)
|
1352
|
-
puts "\nš Performance Trends (Last #{options[:days]} days)".colorize(:cyan)
|
1353
|
-
puts '=' * 80
|
1354
|
-
|
1355
|
-
if trends[:trends].empty?
|
1356
|
-
puts 'No trend data available'
|
1357
|
-
return
|
144
|
+
puts
|
145
|
+
|
146
|
+
hosts.each do |name, config|
|
147
|
+
commands = self.class.kdeploy_tasks[task_name][:block].call
|
148
|
+
output = commands.map do |command|
|
149
|
+
case command[:type]
|
150
|
+
when :run
|
151
|
+
"#{pastel.green('>')} #{command[:command]}"
|
152
|
+
when :upload
|
153
|
+
"#{pastel.blue('>')} Upload: #{command[:source]} -> #{command[:destination]}"
|
154
|
+
end
|
155
|
+
end.join("\n")
|
156
|
+
|
157
|
+
puts TTY::Box.frame(
|
158
|
+
output,
|
159
|
+
title: { top_left: " #{name} (#{config[:ip]}) " },
|
160
|
+
style: {
|
161
|
+
border: {
|
162
|
+
fg: :yellow
|
163
|
+
}
|
164
|
+
}
|
165
|
+
)
|
166
|
+
puts
|
1358
167
|
end
|
1359
|
-
|
1360
|
-
print_trend_header
|
1361
|
-
trends[:trends].each { |date, stats| print_trend_row(date, stats) }
|
1362
|
-
puts ''
|
1363
|
-
end
|
1364
|
-
|
1365
|
-
def print_trend_header
|
1366
|
-
printf "%-12s %10s %10s %10s %12s %12s\n",
|
1367
|
-
'Date', 'Total', 'Success', 'Failed', 'Success Rate', 'Avg Duration'
|
1368
|
-
puts '-' * 78
|
1369
|
-
end
|
1370
|
-
|
1371
|
-
def print_trend_row(date, stats)
|
1372
|
-
printf "%-12s %10d %10d %10d %11.1f%% %11.2fs\n",
|
1373
|
-
date,
|
1374
|
-
stats[:total],
|
1375
|
-
stats[:successful],
|
1376
|
-
stats[:failed],
|
1377
|
-
stats[:success_rate],
|
1378
|
-
stats[:avg_duration]
|
1379
|
-
end
|
1380
|
-
|
1381
|
-
def print_global_table(global_summary)
|
1382
|
-
puts "\nš Global Statistics".colorize(:cyan)
|
1383
|
-
puts '=' * 60
|
1384
|
-
|
1385
|
-
print_global_deployment_stats(global_summary)
|
1386
|
-
print_global_task_stats(global_summary)
|
1387
|
-
print_global_command_stats(global_summary)
|
1388
|
-
print_global_execution_stats(global_summary)
|
1389
168
|
end
|
1390
169
|
|
1391
|
-
def
|
1392
|
-
puts
|
1393
|
-
|
1394
|
-
puts " Successful: #{summary[:successful_deployments]}"
|
1395
|
-
puts " Failed: #{summary[:failed_deployments]}"
|
1396
|
-
end
|
1397
|
-
|
1398
|
-
def print_global_task_stats(summary)
|
1399
|
-
puts "\nTasks:"
|
1400
|
-
puts " Total: #{summary[:total_tasks]}"
|
1401
|
-
puts " Successful: #{summary[:successful_tasks]}"
|
1402
|
-
puts " Failed: #{summary[:failed_tasks]}"
|
1403
|
-
end
|
170
|
+
def print_results(results)
|
171
|
+
puts Kdeploy::Banner.show
|
172
|
+
pastel = Pastel.new
|
1404
173
|
|
1405
|
-
|
1406
|
-
|
1407
|
-
|
1408
|
-
|
1409
|
-
|
1410
|
-
|
1411
|
-
|
1412
|
-
def print_global_execution_stats(summary)
|
1413
|
-
puts "\nExecution Time:"
|
1414
|
-
puts " Total: #{format_duration(summary[:total_execution_time])}"
|
1415
|
-
puts " Session: #{format_duration(summary[:session_duration])}"
|
1416
|
-
puts " Session Started: #{summary[:session_start_time].strftime('%Y-%m-%d %H:%M:%S')}"
|
1417
|
-
puts ''
|
1418
|
-
end
|
174
|
+
results.each do |host, result|
|
175
|
+
status = if result[:status] == :success
|
176
|
+
pastel.green('ā Success')
|
177
|
+
else
|
178
|
+
pastel.red('ā Failed')
|
179
|
+
end
|
1419
180
|
|
1420
|
-
|
1421
|
-
return '0s' if seconds.nil? || seconds.zero?
|
181
|
+
puts "#{pastel.bright_white(host)} - #{status}"
|
1422
182
|
|
1423
|
-
|
1424
|
-
|
1425
|
-
|
1426
|
-
|
1427
|
-
|
1428
|
-
|
183
|
+
if result[:status] == :success
|
184
|
+
result[:output].each do |cmd|
|
185
|
+
puts " #{pastel.bright_yellow('$')} #{cmd[:command]}"
|
186
|
+
if cmd[:output].is_a?(Hash)
|
187
|
+
puts " #{cmd[:output][:stdout]}" unless cmd[:output][:stdout].empty?
|
188
|
+
puts " #{pastel.red(cmd[:output][:stderr])}" unless cmd[:output][:stderr].empty?
|
189
|
+
elsif cmd[:output]
|
190
|
+
puts " #{cmd[:output]}"
|
191
|
+
end
|
192
|
+
puts
|
193
|
+
end
|
194
|
+
else
|
195
|
+
puts " #{pastel.red(result[:error])}"
|
196
|
+
end
|
197
|
+
puts
|
1429
198
|
end
|
1430
199
|
end
|
1431
|
-
|
1432
|
-
def format_minutes(seconds)
|
1433
|
-
minutes = (seconds / 60).to_i
|
1434
|
-
remaining_seconds = (seconds % 60).to_i
|
1435
|
-
"#{minutes}m #{remaining_seconds}s"
|
1436
|
-
end
|
1437
|
-
|
1438
|
-
def format_hours(seconds)
|
1439
|
-
hours = (seconds / 3600).to_i
|
1440
|
-
remaining_minutes = ((seconds % 3600) / 60).to_i
|
1441
|
-
"#{hours}h #{remaining_minutes}m"
|
1442
|
-
end
|
1443
|
-
|
1444
|
-
def generate_export_filename
|
1445
|
-
"kdeploy_stats_#{Time.now.strftime('%Y%m%d_%H%M%S')}.json"
|
1446
|
-
end
|
1447
|
-
|
1448
|
-
def determine_export_format(filename)
|
1449
|
-
File.extname(filename) == '.csv' ? :csv : :json
|
1450
|
-
end
|
1451
200
|
end
|
1452
201
|
end
|