kdeploy 0.1.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.
@@ -0,0 +1,1452 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Add String truncate method if not available
4
+ class String
5
+ def truncate(length)
6
+ return self if size <= length
7
+
8
+ "#{self[0, length - 3]}..."
9
+ end
10
+ end
11
+
12
+ module Kdeploy
13
+ # Command Line Interface for kdeploy
14
+ class CLI < Thor
15
+ # Fix Thor deprecation warning
16
+ def self.exit_on_failure?
17
+ true
18
+ end
19
+
20
+ # Common options for commands that execute scripts
21
+ SCRIPT_OPTIONS = {
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
98
+
99
+ desc 'version', 'Show version'
100
+ def version
101
+ show_kdeploy_banner
102
+ display_version_info
103
+ end
104
+
105
+ desc 'help [COMMAND]', 'Describe available commands or one specific command'
106
+ def help(command = nil)
107
+ show_kdeploy_banner
108
+ puts ''
109
+ puts 'šŸ“– Available Commands:'.colorize(:yellow)
110
+ puts ''
111
+
112
+ if command
113
+ super
114
+ else
115
+ display_available_commands
116
+ end
117
+ end
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
155
+
156
+ display_execution_settings(config)
157
+ display_network_settings(config)
158
+ display_file_settings(config)
159
+ display_logging_settings(config)
160
+ display_ssh_options(config) if config.respond_to?(:ssh_options) && config.ssh_options&.any?
161
+
162
+ puts ''
163
+ puts 'šŸ’” Use --config FILE to load custom configuration'.colorize(:yellow)
164
+ puts ''
165
+ end
166
+
167
+ def display_execution_settings(config)
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
174
+
175
+ def display_network_settings(config)
176
+ puts ''
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
183
+
184
+ def display_file_settings(config)
185
+ puts ''
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
190
+
191
+ def display_logging_settings(config)
192
+ puts ''
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
197
+
198
+ def display_ssh_options(config)
199
+ puts ''
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
205
+
206
+ def display_version_info
207
+ puts ''
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
214
+
215
+ def display_available_commands
216
+ puts " šŸš€ #{'deploy SCRIPT'.ljust(25)} Execute deployment script"
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
228
+
229
+ def setup_configuration
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]
235
+ end
236
+ end
237
+
238
+ def setup_logging
239
+ config = Kdeploy.configuration
240
+ KdeployLogger.setup(
241
+ level: config.log_level,
242
+ log_file: config.log_file
243
+ )
244
+ end
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"
639
+ end
640
+
641
+ def create_monitoring_script(project_name)
642
+ content = <<~RUBY
643
+ # frozen_string_literal: true
644
+
645
+ # Health checks and monitoring script
646
+ pipeline 'monitoring' do
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
673
+
674
+ task :check_logs do
675
+ run <<~BASH
676
+ echo "Last 50 lines of application log:"
677
+ tail -n 50 /var/www/app/current/log/production.log
678
+ echo "\\nLast 50 lines of nginx error log:"
679
+ sudo tail -n 50 /var/log/nginx/error.log
680
+ BASH
681
+ end
682
+
683
+ task :check_database do
684
+ only :db
685
+ run <<~BASH
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
692
+
693
+ task :monitor do
694
+ depends_on :check_system_resources,
695
+ :check_services,
696
+ :check_logs,
697
+ :check_database
698
+ end
699
+ 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
+
710
+ # Rollback operations script
711
+ pipeline 'rollback' do
712
+ # Target application hosts
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
747
+ 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
+
778
+ task :cleanup_logs do
779
+ run <<~BASH
780
+ find /var/www/app/current/log -name "*.log.*" -mtime +${keep_logs} -exec rm {} \\;
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)
1070
+ end
1071
+ end
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")}")
1095
+ exit 1
1096
+ end
1097
+
1098
+ def show_summary_stats
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
1169
+
1170
+ case options[:format].downcase
1171
+ when 'json'
1172
+ puts JSON.pretty_generate(global_summary)
1173
+ else
1174
+ print_global_table(global_summary)
1175
+ end
1176
+ end
1177
+
1178
+ def clear_statistics
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
1187
+ end
1188
+
1189
+ def export_statistics
1190
+ show_kdeploy_banner
1191
+ export_file = options[:output] || generate_export_filename
1192
+ format = determine_export_format(export_file)
1193
+
1194
+ Kdeploy.statistics.export_statistics(export_file, format: format)
1195
+ success "Statistics exported to #{export_file}"
1196
+ end
1197
+
1198
+ def display_json_summary(deployment_summary, task_summary, global_summary)
1199
+ puts JSON.pretty_generate(
1200
+ {
1201
+ deployment_summary: deployment_summary,
1202
+ task_summary: task_summary,
1203
+ global_summary: global_summary
1204
+ }
1205
+ )
1206
+ end
1207
+
1208
+ def print_summary_table(deployment_summary, task_summary, global_summary)
1209
+ puts "\nšŸ“Š Kdeploy Statistics Summary (Last #{options[:days]} days)".colorize(:cyan)
1210
+ puts '=' * 60
1211
+
1212
+ print_deployment_section(deployment_summary)
1213
+ print_task_section(task_summary)
1214
+ print_global_section(global_summary)
1215
+ end
1216
+
1217
+ def print_deployment_section(summary)
1218
+ puts "\nšŸ“¦ Deployment Statistics".colorize(:yellow)
1219
+ if summary[:total_deployments].positive?
1220
+ puts " Total Deployments: #{summary[:total_deployments]}"
1221
+ puts " Successful: #{summary[:successful_deployments]} (#{summary[:success_rate]}%)"
1222
+ puts " Failed: #{summary[:failed_deployments]}"
1223
+ puts " Average Duration: #{summary[:avg_duration]}s"
1224
+ puts " Total Duration: #{format_duration(summary[:total_duration])}"
1225
+ else
1226
+ puts " No deployments in the last #{options[:days]} days"
1227
+ end
1228
+ end
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
1358
+ 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
+ end
1390
+
1391
+ def print_global_deployment_stats(summary)
1392
+ puts 'Deployments:'
1393
+ puts " Total: #{summary[:total_deployments]}"
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
1404
+
1405
+ def print_global_command_stats(summary)
1406
+ puts "\nCommands:"
1407
+ puts " Total: #{summary[:total_commands]}"
1408
+ puts " Successful: #{summary[:successful_commands]}"
1409
+ puts " Failed: #{summary[:failed_commands]}"
1410
+ end
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
1419
+
1420
+ def format_duration(seconds)
1421
+ return '0s' if seconds.nil? || seconds.zero?
1422
+
1423
+ if seconds < 60
1424
+ "#{seconds.round(1)}s"
1425
+ elsif seconds < 3600
1426
+ format_minutes(seconds)
1427
+ else
1428
+ format_hours(seconds)
1429
+ end
1430
+ 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
+ end
1452
+ end