kdeploy 1.2.1 → 1.2.2

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,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pastel'
4
+
5
+ module Kdeploy
6
+ # Formats help text for CLI
7
+ class HelpFormatter
8
+ def initialize
9
+ @pastel = Pastel.new
10
+ end
11
+
12
+ def format_help
13
+ <<~HELP
14
+ #{@pastel.bright_white('📖 Available Commands:')}
15
+
16
+ #{format_commands}
17
+
18
+ #{format_examples}
19
+
20
+ #{format_documentation}
21
+ HELP
22
+ end
23
+
24
+ private
25
+
26
+ def format_commands
27
+ <<~COMMANDS
28
+ #{@pastel.bright_yellow('🚀')} #{@pastel.bright_white('execute TASK_FILE [TASK]')} Execute deployment tasks from file
29
+ #{@pastel.dim(' --limit HOSTS')} Limit to specific hosts (comma-separated)
30
+ #{@pastel.dim(' --parallel NUM')} Number of parallel executions (default: 5)
31
+ #{@pastel.dim(' --dry-run')} Show what would be done without executing
32
+
33
+ #{@pastel.bright_yellow('🆕')} #{@pastel.bright_white('init [DIR]')} Initialize new deployment project
34
+ #{@pastel.bright_yellow('â„šī¸')} #{@pastel.bright_white('version')} Show version information
35
+ #{@pastel.bright_yellow('❓')} #{@pastel.bright_white('help [COMMAND]')} Show help information
36
+ COMMANDS
37
+ end
38
+
39
+ def format_examples
40
+ <<~EXAMPLES
41
+ #{@pastel.bright_white('💡 Examples:')}
42
+
43
+ #{@pastel.dim('# Initialize a new project')}
44
+ #{@pastel.bright_cyan('kdeploy init my-deployment')}
45
+
46
+ #{@pastel.dim('# Deploy to web servers')}
47
+ #{@pastel.bright_cyan('kdeploy execute deploy.rb deploy_web')}
48
+
49
+ #{@pastel.dim('# Backup database')}
50
+ #{@pastel.bright_cyan('kdeploy execute deploy.rb backup_db')}
51
+
52
+ #{@pastel.dim('# Run maintenance on specific hosts')}
53
+ #{@pastel.bright_cyan('kdeploy execute deploy.rb maintenance --limit web01')}
54
+
55
+ #{@pastel.dim('# Preview deployment')}
56
+ #{@pastel.bright_cyan('kdeploy execute deploy.rb deploy_web --dry-run')}
57
+ EXAMPLES
58
+ end
59
+
60
+ def format_documentation
61
+ <<~DOCS
62
+ #{@pastel.bright_white('📚 Documentation:')}
63
+ #{@pastel.bright_cyan('https://github.com/kevin197011/kdeploy')}
64
+ DOCS
65
+ end
66
+ end
67
+ end
@@ -21,6 +21,7 @@ module Kdeploy
21
21
  # Console output implementation
22
22
  class ConsoleOutput < Output
23
23
  def initialize
24
+ super
24
25
  @pastel = Pastel.new
25
26
  end
26
27
 
@@ -44,6 +45,7 @@ module Kdeploy
44
45
  attr_reader :messages, :errors
45
46
 
46
47
  def initialize
48
+ super
47
49
  @messages = []
48
50
  @errors = []
49
51
  end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pastel'
4
+ require 'tty-box'
5
+
6
+ module Kdeploy
7
+ # Formats and displays execution results
8
+ class OutputFormatter
9
+ def initialize
10
+ @pastel = Pastel.new
11
+ end
12
+
13
+ def format_task_header(task_name)
14
+ @pastel.cyan("\nPLAY [#{task_name}] " + ('*' * 64))
15
+ end
16
+
17
+ def format_host_status(host, status)
18
+ status_str = case status
19
+ when :success then @pastel.green('ok')
20
+ when :changed then @pastel.yellow('changed')
21
+ else @pastel.red('failed')
22
+ end
23
+ @pastel.bright_white("\n#{host.ljust(24)} : #{status_str}")
24
+ end
25
+
26
+ def format_upload_steps(steps, shown)
27
+ format_file_steps(steps, shown, :upload, @pastel.green(' === Upload ==='), 'upload: ')
28
+ end
29
+
30
+ def format_template_steps(steps, shown)
31
+ format_file_steps(steps, shown, :upload_template, @pastel.yellow(' === Template ==='), 'upload_template: ')
32
+ end
33
+
34
+ def format_file_steps(steps, shown, type, header, prefix)
35
+ output = [header]
36
+ steps.each do |step|
37
+ next if step_already_shown?(step, type, shown)
38
+
39
+ mark_step_as_shown(step, type, shown)
40
+ output << format_file_step(step, type, prefix)
41
+ end
42
+ output
43
+ end
44
+
45
+ def format_file_step(step, type, prefix)
46
+ duration_str = format_duration(step[:duration])
47
+ type == :upload ? @pastel.green : @pastel.yellow
48
+ label = type == :upload ? '[upload]' : '[template]'
49
+ color(" #{label} #{step[:command].sub(prefix, '')}#{duration_str}")
50
+ end
51
+
52
+ def format_run_steps(steps, shown)
53
+ output = []
54
+ output << @pastel.cyan(' === Run ===')
55
+ steps.each do |step|
56
+ next if step_already_shown?(step, :run, shown)
57
+
58
+ mark_step_as_shown(step, :run, shown)
59
+ output.concat(format_single_run_step(step))
60
+ end
61
+ output
62
+ end
63
+
64
+ def format_single_run_step(step)
65
+ output = []
66
+ duration_str = format_duration(step[:duration])
67
+ command_line = step[:command].to_s.lines.first.strip
68
+ output << @pastel.cyan(" [run] #{command_line}#{duration_str}")
69
+ output.concat(format_multiline_command(step[:command]))
70
+ output.concat(format_command_output(step[:output]))
71
+ output
72
+ end
73
+
74
+ def format_error(error_message)
75
+ @pastel.red(" ERROR: #{error_message}")
76
+ end
77
+
78
+ def format_summary_header
79
+ @pastel.cyan("\nPLAY RECAP #{'*' * 64}")
80
+ end
81
+
82
+ def format_summary_line(host, result, max_host_len)
83
+ counts = calculate_summary_counts(result)
84
+ line = build_summary_line(host, counts, max_host_len)
85
+ colorize_summary_line(line, counts)
86
+ end
87
+
88
+ def calculate_summary_counts(result)
89
+ ok = %i[success changed].include?(result[:status]) ? result[:output].size : 0
90
+ failed = result[:status] == :failed ? 1 : 0
91
+ changed = result[:status] == :changed ? result[:output].size : 0
92
+ { ok: ok, failed: failed, changed: changed }
93
+ end
94
+
95
+ def build_summary_line(host, counts, max_host_len)
96
+ ok_w = 7
97
+ changed_w = 11
98
+ failed_w = 10
99
+
100
+ ok_str = @pastel.green("ok=#{counts[:ok].to_s.ljust(ok_w - 3)}")
101
+ changed_str = @pastel.yellow("changed=#{counts[:changed].to_s.ljust(changed_w - 8)}")
102
+ failed_str = @pastel.red("failed=#{counts[:failed].to_s.ljust(failed_w - 7)}")
103
+ "#{host.ljust(max_host_len)} : #{ok_str} #{changed_str} #{failed_str}"
104
+ end
105
+
106
+ def colorize_summary_line(line, counts)
107
+ if counts[:failed].positive?
108
+ @pastel.red(line)
109
+ elsif counts[:ok].positive? && counts[:failed].zero?
110
+ @pastel.green(line)
111
+ else
112
+ line
113
+ end
114
+ end
115
+
116
+ def format_dry_run_box(title, content)
117
+ TTY::Box.frame(
118
+ content,
119
+ title: { top_left: " #{title} " },
120
+ style: {
121
+ border: {
122
+ fg: :yellow
123
+ }
124
+ }
125
+ )
126
+ end
127
+
128
+ def format_dry_run_header
129
+ TTY::Box.frame(
130
+ 'Showing what would be done without executing any commands',
131
+ title: { top_left: ' Dry Run Mode ', bottom_right: ' Kdeploy ' },
132
+ style: {
133
+ border: {
134
+ fg: :blue
135
+ }
136
+ }
137
+ )
138
+ end
139
+
140
+ def format_command_for_dry_run(command)
141
+ case command[:type]
142
+ when :run
143
+ "#{@pastel.green('>')} #{command[:command]}"
144
+ when :upload
145
+ "#{@pastel.blue('>')} Upload: #{command[:source]} -> #{command[:destination]}"
146
+ when :upload_template
147
+ "#{@pastel.blue('>')} Template: #{command[:source]} -> #{command[:destination]}"
148
+ else
149
+ "#{@pastel.blue('>')} #{command[:type]}: #{command}"
150
+ end
151
+ end
152
+
153
+ private
154
+
155
+ def format_duration(duration)
156
+ duration ? @pastel.dim(" [#{format('%.2f', duration)}s]") : ''
157
+ end
158
+
159
+ def format_multiline_command(command)
160
+ output = []
161
+ cmd_lines = command.to_s.lines[1..].map(&:strip).reject(&:empty?)
162
+ cmd_lines.each { |line| output << @pastel.cyan(" > #{line}") } if cmd_lines.any?
163
+ output
164
+ end
165
+
166
+ def format_command_output(output)
167
+ result = []
168
+ return result unless output
169
+
170
+ if output.is_a?(Hash) && output[:stdout]
171
+ format_stdout_lines(output[:stdout], result)
172
+ elsif output.is_a?(String)
173
+ format_stdout_lines(output, result)
174
+ end
175
+ result
176
+ end
177
+
178
+ def format_stdout_lines(stdout, result)
179
+ stdout.each_line do |line|
180
+ result << @pastel.green(" #{line.rstrip}") unless line.strip.empty?
181
+ end
182
+ end
183
+
184
+ def step_already_shown?(step, type, shown)
185
+ key = [step[:command], type].hash
186
+ shown[key]
187
+ end
188
+
189
+ def mark_step_as_shown(step, type, shown)
190
+ key = [step[:command], type].hash
191
+ shown[key] = true
192
+ end
193
+ end
194
+ end
@@ -15,19 +15,31 @@ module Kdeploy
15
15
  end
16
16
 
17
17
  def run(task_name)
18
+ task = find_task(task_name)
19
+ execute_concurrent_tasks(task)
20
+ ensure
21
+ @pool.shutdown
22
+ end
23
+
24
+ def find_task(task_name)
18
25
  task = @tasks[task_name]
19
26
  raise TaskNotFoundError, task_name unless task
20
27
 
21
- futures = @hosts.map do |name, config|
28
+ task
29
+ end
30
+
31
+ def execute_concurrent_tasks(task)
32
+ futures = create_task_futures(task)
33
+ futures.each(&:wait)
34
+ @results
35
+ end
36
+
37
+ def create_task_futures(task)
38
+ @hosts.map do |name, config|
22
39
  Concurrent::Future.execute(executor: @pool) do
23
40
  execute_task_for_host(name, config, task)
24
41
  end
25
42
  end
26
-
27
- futures.each(&:wait)
28
- @results
29
- ensure
30
- @pool.shutdown
31
43
  end
32
44
 
33
45
  private
@@ -37,23 +49,30 @@ module Kdeploy
37
49
  command_executor = CommandExecutor.new(executor, @output)
38
50
  result = { status: :success, output: [] }
39
51
 
52
+ execute_grouped_commands(task, command_executor, name, result)
53
+ @results[name] = result
54
+ rescue StandardError => e
55
+ @results[name] = { status: :failed, error: e.message }
56
+ end
57
+
58
+ def execute_grouped_commands(task, command_executor, name, result)
40
59
  commands = task[:block].call
41
60
  grouped_commands = CommandGrouper.group(commands)
42
61
 
43
62
  grouped_commands.each_value do |command_group|
44
- first_cmd = command_group.first
45
- task_desc = CommandGrouper.task_description(first_cmd)
46
- show_task_header(task_desc)
47
-
48
- command_group.each do |command|
49
- step_result = execute_command(command_executor, command, name)
50
- result[:output] << step_result
51
- end
63
+ execute_command_group(command_group, command_executor, name, result)
52
64
  end
65
+ end
53
66
 
54
- @results[name] = result
55
- rescue StandardError => e
56
- @results[name] = { status: :failed, error: e.message }
67
+ def execute_command_group(command_group, command_executor, name, result)
68
+ first_cmd = command_group.first
69
+ task_desc = CommandGrouper.task_description(first_cmd)
70
+ show_task_header(task_desc)
71
+
72
+ command_group.each do |command|
73
+ step_result = execute_command(command_executor, command, name)
74
+ result[:output] << step_result
75
+ end
57
76
  end
58
77
 
59
78
  def execute_command(command_executor, command, host_name)
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'erb'
4
- require 'ostruct'
5
4
  require 'tempfile'
6
5
 
7
6
  module Kdeploy
@@ -9,10 +8,19 @@ module Kdeploy
9
8
  class Template
10
9
  def self.render(template_path, variables = {})
11
10
  template_content = File.read(template_path)
12
- context = OpenStruct.new(variables)
11
+ context = create_template_context(variables)
13
12
  ERB.new(template_content).result(context.instance_eval { binding })
14
13
  end
15
14
 
15
+ def self.create_template_context(variables)
16
+ # Use a simple class instead of OpenStruct for better performance
17
+ context_class = Class.new
18
+ variables.each do |key, value|
19
+ context_class.define_method(key) { value }
20
+ end
21
+ context_class.new
22
+ end
23
+
16
24
  def self.render_and_upload(executor, template_path, destination, variables = {})
17
25
  rendered_content = render(template_path, variables)
18
26
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kdeploy
4
- VERSION = '1.2.1'
4
+ VERSION = '1.2.2'
5
5
  end
data/lib/kdeploy.rb CHANGED
@@ -9,6 +9,8 @@ require_relative 'kdeploy/dsl'
9
9
  require_relative 'kdeploy/executor'
10
10
  require_relative 'kdeploy/command_grouper'
11
11
  require_relative 'kdeploy/command_executor'
12
+ require_relative 'kdeploy/output_formatter'
13
+ require_relative 'kdeploy/help_formatter'
12
14
  require_relative 'kdeploy/runner'
13
15
  require_relative 'kdeploy/initializer'
14
16
  require_relative 'kdeploy/template'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kdeploy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kk
@@ -132,7 +132,9 @@ extensions:
132
132
  - ext/mkrf_conf.rb
133
133
  extra_rdoc_files: []
134
134
  files:
135
+ - OPTIMIZATION_COMPLETE.md
135
136
  - README.md
137
+ - REFACTORING_SUMMARY.md
136
138
  - exe/kdeploy
137
139
  - ext/mkrf_conf.rb
138
140
  - lib/kdeploy.rb
@@ -146,8 +148,10 @@ files:
146
148
  - lib/kdeploy/dsl.rb
147
149
  - lib/kdeploy/errors.rb
148
150
  - lib/kdeploy/executor.rb
151
+ - lib/kdeploy/help_formatter.rb
149
152
  - lib/kdeploy/initializer.rb
150
153
  - lib/kdeploy/output.rb
154
+ - lib/kdeploy/output_formatter.rb
151
155
  - lib/kdeploy/post_install.rb
152
156
  - lib/kdeploy/runner.rb
153
157
  - lib/kdeploy/template.rb