choria-colt 0.7.0 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e86ae8b9274433cf146d571d2a7466a2c7c131e826ec5e4a8df9540d37f7994d
4
- data.tar.gz: 9e4717f11b4f1f3f93c555818b9281e0118ac59a4bbf7b2573d6a85ed6e74fd9
3
+ metadata.gz: 5791ce130daf3f0d0300f6ca56fa07f88c6a94e08ff4979d84282526220a3eb4
4
+ data.tar.gz: d4ce31439d50ba72bff357e13901c4621a1a8f79ce0a8458ed1b0c948e22f7e0
5
5
  SHA512:
6
- metadata.gz: 3754982bc70a01d44fc4115c76693f67201d5a618d2f61b229d9b3408bea3ff2c2ac187ea5c0bd55fdbf8ca391c668f09086339ab366a85f93100c0ac1bd8973
7
- data.tar.gz: c1a1dd4eaf6287d28d09baa7f45ff7ab297ffff5a8bd78d519d9a814dfcab74512d6f300fe6696789d1687e1a9f04aa021f90531a98143e263fae4d3a8d53688
6
+ metadata.gz: 427751ad2b8a07a0426c74aad97c2f67c2fa27b151f63db96e63d8116c15f39615ae40dd76d6aa3c1f6ad92c95c27219eb312bdb0f43f281b05ab8bd3cd07c12
7
+ data.tar.gz: bd056cdb49de93481735ba4ece34ed40c48ac7d728c31ec8d0066aa300ecb7bed055f5d541c6973e3459b292475497ce4ef5357ae5cb8e3ec681a66e29b4684f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.8.0](https://github.com/opus-codium/choria-colt/tree/v0.8.0) (2022-11-25)
4
+
5
+ [Full Changelog](https://github.com/opus-codium/choria-colt/compare/v0.7.0...v0.8.0)
6
+
7
+ **Merged pull requests:**
8
+
9
+ - CLI: Summarize task status results by default [\#33](https://github.com/opus-codium/choria-colt/pull/33) ([neomilium](https://github.com/neomilium))
10
+ - CLI: Always display stderr if available [\#32](https://github.com/opus-codium/choria-colt/pull/32) ([neomilium](https://github.com/neomilium))
11
+
3
12
  ## [v0.7.0](https://github.com/opus-codium/choria-colt/tree/v0.7.0) (2022-09-26)
4
13
 
5
14
  [Full Changelog](https://github.com/opus-codium/choria-colt/compare/v0.6.0...v0.7.0)
data/choria-colt.gemspec CHANGED
@@ -35,6 +35,7 @@ Gem::Specification.new do |spec|
35
35
  spec.add_dependency 'puppet'
36
36
  spec.add_dependency 'thor'
37
37
  spec.add_dependency 'tty-logger'
38
+ spec.add_dependency 'tty-progressbar'
38
39
 
39
40
  # spec.add_development_dependency 'byebug'
40
41
  spec.add_development_dependency 'github_changelog_generator'
@@ -22,6 +22,10 @@ module Choria
22
22
  dig(:data, :runtime)
23
23
  end
24
24
 
25
+ def statuscode
26
+ self[:statuscode]
27
+ end
28
+
25
29
  # CLI
26
30
  def output
27
31
  if dig(:result, :_output).nil?
@@ -30,92 +34,131 @@ module Choria
30
34
  dig(:result, :_output)
31
35
  end
32
36
  end
37
+
38
+ def stderr
39
+ dig(:result, :_stderr)
40
+ end
33
41
  end
34
42
 
35
- attr_reader :pastel
43
+ module FormattedResult
44
+ attr_accessor :pastel
36
45
 
37
- def initialize(colored:)
38
- @pastel = Pastel.new(enabled: colored)
39
- pastel.alias_color(:host, :cyan)
40
- end
46
+ def host
47
+ if statuscode.zero?
48
+ format_host pastel.bright_green('√ ').to_s
49
+ else
50
+ format_host pastel.bright_red('⨯ ').to_s
51
+ end
52
+ end
41
53
 
42
- def process_result(result)
43
- result.extend Formatter::Result
54
+ def content
55
+ case statuscode
56
+ when 0
57
+ # 0 OK
58
+ format_success
59
+ when 1
60
+ # 1 OK, failed. All the data parsed ok, we have a action matching the request but the requested action could not be completed. RPCAborted
61
+ format_error
62
+ else
63
+ # 2 Unknown action UnknownRPCAction
64
+ # 3 Missing data MissingRPCData
65
+ # 4 Invalid data InvalidRPCData
66
+ # 5 Other error
67
+ format_rpc_error
68
+ end
69
+ end
44
70
 
45
- case result[:statuscode]
46
- when 0
47
- # 0 OK
48
- process_success(result)
49
- when 1
50
- # 1 OK, failed. All the data parsed ok, we have a action matching the request but the requested action could not be completed. RPCAborted
51
- process_error(result) # unless result.ok?
52
- else
53
- # 2 Unknown action UnknownRPCAction
54
- # 3 Missing data MissingRPCData
55
- # 4 Invalid data InvalidRPCData
56
- # 5 Other error
57
- process_rpc_error(result)
71
+ def to_s
72
+ [
73
+ host,
74
+ content,
75
+ ].join("\n")
58
76
  end
59
- end
60
77
 
61
- def process_success(result)
62
- host = format_host(result, "#{pastel.bright_green '√'} ")
63
- headline = "#{pastel.on_green ' '} "
78
+ private
64
79
 
65
- [
66
- host,
67
- result.output.map { |line| "#{headline}#{line}" },
68
- ].flatten.join("\n")
69
- end
80
+ def stderr_description
81
+ if stderr.nil? || stderr.empty?
82
+ []
83
+ else
84
+ [
85
+ nil,
86
+ pastel.bright_red('stderr:'),
87
+ stderr,
88
+ ]
89
+ end
90
+ end
91
+
92
+ def output_description
93
+ if output.nil? || output.empty?
94
+ []
95
+ else
96
+ [
97
+ nil,
98
+ pastel.bright_red('output:'),
99
+ output,
100
+ ]
101
+ end
102
+ end
103
+
104
+ def format_duration
105
+ runtime.nil? ? '' : "duration: #{pastel.bright_white format('%.2fs', runtime)}"
106
+ end
107
+
108
+ def format_host(headline)
109
+ "#{headline}#{pastel.host(sender).ljust(60, ' ')}#{format_duration}".strip
110
+ end
111
+
112
+ def format_success
113
+ headline = "#{pastel.on_green ' '} "
114
+ warning_headline = "#{pastel.on_yellow ' '} "
70
115
 
71
- def process_error(result) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
72
- host = format_host(result, "#{pastel.bright_red '⨯'} ")
73
- output = result.dig(:result, '_output')
74
- error_details = JSON.pretty_generate(result.dig(:result, :_error, :details)).split "\n"
75
- error_description = [
76
- "#{pastel.bright_red result.dig(:result, :_error, :kind)}: #{pastel.bright_white result.dig(:result, :_error, :msg)}",
77
- " details: #{error_details.shift}",
78
- error_details.map { |line| " #{line}" },
79
- ]
80
- output_description = if output.nil? || output.empty?
81
- []
82
- else
83
- [
84
- nil,
85
- pastel.bright_red('output:'),
86
- output,
87
- ]
88
- end
89
-
90
- headline = "#{pastel.on_red ' '} "
91
-
92
- [
93
- host,
94
116
  [
95
- error_description,
96
- output_description,
97
- ].flatten.map { |line| "#{headline}#{line}" },
98
- ].flatten.join("\n")
99
- end
117
+ output.map { |line| "#{headline}#{line}" },
118
+ stderr_description.flatten.map { |line| "#{warning_headline}#{line}" },
119
+ ].flatten.join("\n")
120
+ end
121
+
122
+ def format_error
123
+ error_details = JSON.pretty_generate(dig(:result, :_error, :details)).split "\n"
124
+ error_description = [
125
+ "#{pastel.bright_red dig(:result, :_error, :kind)}: #{pastel.bright_white dig(:result, :_error, :msg)}",
126
+ " details: #{error_details.shift}",
127
+ error_details.map { |line| " #{line}" },
128
+ ]
100
129
 
101
- def process_rpc_error(result)
102
- host = "#{pastel.bright_red '⨯'} #{pastel.host(result.sender)}"
103
- headline = "#{pastel.on_red ' '} "
130
+ headline = "#{pastel.on_red ' '} "
104
131
 
105
- [
106
- host,
107
- "#{headline}#{pastel.bright_red "RPC error (#{result[:statuscode]})"}: #{pastel.bright_white result[:statusmsg]}",
108
- ].join("\n")
132
+ [
133
+ [
134
+ error_description,
135
+ output_description,
136
+ stderr_description,
137
+ ].flatten.map { |line| "#{headline}#{line}" },
138
+ ].flatten.join("\n")
139
+ end
140
+
141
+ def format_rpc_error
142
+ headline = "#{pastel.on_red ' '} "
143
+
144
+ [
145
+ "#{headline}#{pastel.bright_red "RPC error (#{statuscode})"}: #{pastel.bright_white self[:statusmsg]}",
146
+ ].join("\n")
147
+ end
109
148
  end
110
149
 
111
- private
150
+ attr_reader :pastel
112
151
 
113
- def format_duration(result)
114
- result.runtime.nil? ? '' : "duration: #{pastel.bright_white format('%.2fs', result.runtime)}"
152
+ def initialize(colored:)
153
+ @pastel = Pastel.new(enabled: colored)
154
+ pastel.alias_color(:host, :cyan)
115
155
  end
116
156
 
117
- def format_host(result, headline)
118
- "#{headline}#{pastel.host(result.sender).ljust(60, ' ')}#{format_duration(result)}"
157
+ def format(result)
158
+ result.extend Formatter::Result
159
+ result.extend Formatter::FormattedResult
160
+ result.pastel = pastel
161
+ result
119
162
  end
120
163
  end
121
164
  end
@@ -44,7 +44,7 @@ module Choria
44
44
 
45
45
  environment = options['environment']
46
46
  results = colt.run_bolt_task task_name, input: input, targets: targets, targets_with_classes: targets_with_classes, environment: environment do |result|
47
- $stdout.puts formatter.process_result(result)
47
+ $stdout.puts formatter.format(result)
48
48
  end
49
49
 
50
50
  File.write 'last_run.json', JSON.pretty_generate(results)
@@ -86,12 +86,24 @@ module Choria
86
86
 
87
87
  A task ID is required to request Choria services and retrieve results.
88
88
  DESC
89
+ option :style,
90
+ aliases: ['-S'],
91
+ desc: "Output style; can be 'continuous' or 'summary'",
92
+ default: 'summary'
89
93
  define_targets_and_filters_options
90
94
  def status(task_id)
95
+ supported_styles = %i[summary continous]
96
+ raise Thor::Error, "Invalid style: '#{options['style']}' (available: #{supported_styles})" unless supported_styles.include? options['style'].to_sym
97
+
91
98
  targets, targets_with_classes = extract_targets_and_filters_from_options
92
99
 
93
- results = colt.wait_bolt_task(task_id, targets: targets, targets_with_classes: targets_with_classes) do |result|
94
- $stdout.puts formatter.process_result(result)
100
+ case options['style']
101
+ when 'continous'
102
+ results = colt.wait_bolt_task(task_id, targets: targets, targets_with_classes: targets_with_classes) do |result|
103
+ $stdout.puts formatter.format(result)
104
+ end
105
+ when 'summary'
106
+ show_summarized_status(task_id, targets: targets, targets_with_classes: targets_with_classes)
95
107
  end
96
108
 
97
109
  File.write 'last_run.json', JSON.pretty_generate(results)
@@ -111,6 +123,7 @@ module Choria
111
123
  [:stream, { output: File.open('colt-debug.log', 'a'), level: :debug }],
112
124
  ]
113
125
  config.metadata = %i[date time]
126
+ Choria::Colt::Debugger.enabled = true
114
127
  end
115
128
  end
116
129
 
@@ -185,6 +198,33 @@ module Choria
185
198
  end.join "\n"
186
199
  end
187
200
 
201
+ def summarize(results)
202
+ results_grouped_by_result_content = results.group_by { |result| result[:result] }
203
+ results_grouped_by_result_content.each do |_content, grouped_results|
204
+ # Display each host
205
+ grouped_results.each do |result|
206
+ $stdout.puts formatter.format(result).host
207
+ end
208
+ # Display the result content
209
+ content = formatter.format(grouped_results.first).content
210
+ content = pastel.bright_white '(no output)' if content.nil? || content.empty?
211
+ $stdout.puts content
212
+ end
213
+ end
214
+
215
+ def show_summarized_status(task_id, targets:, targets_with_classes:)
216
+ require 'tty-progressbar'
217
+
218
+ bar = TTY::ProgressBar.new('[:bar] :current/:total ET::elapsed ETA::eta :rate/s')
219
+ results = colt.wait_bolt_task(task_id, targets: targets, targets_with_classes: targets_with_classes) do |_result, count, total_count|
220
+ bar.update total: total_count
221
+ bar.current = count
222
+ end
223
+
224
+ puts "\nSummary:"
225
+ summarize(results)
226
+ end
227
+
188
228
  def pastel
189
229
  @pastel ||= _pastel
190
230
  end
@@ -22,11 +22,7 @@ module Choria
22
22
  # On one side, data.stderr is filled by the remote execution stderr.
23
23
  # On the other side, error description is in JSON (ie. '_error')
24
24
  # So merge data.stderr in '_error'.'details'
25
- unless res.dig(:data, :stderr).nil? || res[:data][:stderr].empty?
26
- raise NotImplementedError, 'What to do when res[:data][:stderr] contains something?' if res[:result]['_error'].empty?
27
-
28
- res[:result]['_error']['details'].merge!({ 'stderr' => res[:data][:stderr].split("\n") })
29
- end
25
+ res[:result]['_stderr'] = res[:data][:stderr].split("\n") unless res.dig(:data, :stderr).nil? || res[:data][:stderr].empty?
30
26
  res[:data].delete :stderr
31
27
 
32
28
  # Convert '_output' (ie. stdout) lines into array
@@ -0,0 +1,28 @@
1
+ module Choria
2
+ class Colt
3
+ module Debugger
4
+ class << self
5
+ attr_writer :enabled
6
+
7
+ def enabled
8
+ @enabled ||= false
9
+ end
10
+
11
+ def root_directory
12
+ 'colt-debug'
13
+ end
14
+
15
+ # This method is helpful to grab raw content to be used as test fixture
16
+ # To do so, copy the generated result set (ie. directory) in relevant fixture directory (e.g. `spec/fixtures/orchestrator/task/result_sets`)
17
+ def save_file(result_set:, filename:, content:)
18
+ directory = File.join Colt::Debugger.root_directory, result_set
19
+ FileUtils.mkdir_p directory
20
+ path = File.join directory, filename
21
+ File.write(path, content)
22
+
23
+ path
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Choria
4
4
  class Colt
5
- VERSION = '0.7.0'
5
+ VERSION = '0.8.0'
6
6
  end
7
7
  end
@@ -6,9 +6,12 @@ module Choria
6
6
  class ResultSet
7
7
  attr_reader :results
8
8
 
9
+ attr_accessor :pending_count
10
+
9
11
  def initialize(on_result:)
10
12
  @results = []
11
13
  @on_result = on_result
14
+ @pending_count = 0
12
15
  end
13
16
 
14
17
  def integrate_rpc_error(rpc_error)
@@ -20,7 +23,7 @@ module Choria
20
23
  def integrate_result(result)
21
24
  structured_result = Choria::Colt::DataStructurer.structure(result).with_indifferent_access
22
25
  @results << structured_result
23
- @on_result&.call(structured_result)
26
+ @on_result&.call(structured_result, @results.count, pending_count + @results.count)
24
27
  end
25
28
  end
26
29
  end
@@ -3,6 +3,8 @@ require_relative 'task/result_set'
3
3
  require 'active_support'
4
4
  require 'active_support/core_ext/hash/indifferent_access'
5
5
 
6
+ require 'choria/colt/debugger'
7
+
6
8
  module Choria
7
9
  class Orchestrator
8
10
  class Task
@@ -55,7 +57,7 @@ module Choria
55
57
  end
56
58
 
57
59
  def on_result(&block)
58
- @on_result = ->(result) { block.call(result) }
60
+ @on_result = ->(result, count, total_count) { block.call(result, count, total_count) }
59
61
  end
60
62
 
61
63
  private
@@ -64,15 +66,26 @@ module Choria
64
66
  @result_set ||= ResultSet.new(on_result: @on_result)
65
67
  end
66
68
 
69
+ def log_new_result(res)
70
+ if Colt::Debugger.enabled
71
+ debug_file = Colt::Debugger.save_file(result_set: @id, filename: "#{Time.now.iso8601}-#{res[:sender]}.json", content: JSON.pretty_generate(res))
72
+ logger.debug "New result for task ##{@id} saved in '#{debug_file}'"
73
+ else
74
+ logger.debug "New result for task ##{@id} from '#{res[:sender]}'"
75
+ end
76
+ end
77
+
67
78
  def rpc_results=(results)
68
79
  completed_results = results.reject { |res| res[:data][:exitcode] == -1 }
69
80
  @pending_targets ||= results.map { |res| res[:sender] }
70
81
 
71
82
  new_results = completed_results.select { |res| @pending_targets.include? res[:sender] }
72
83
  new_results.each do |res|
73
- logger.debug "New result for task ##{@id}: #{res}"
74
- result_set.integrate_result(res)
84
+ log_new_result res
85
+
75
86
  @pending_targets.delete res[:sender]
87
+ result_set.pending_count = @pending_targets.count
88
+ result_set.integrate_result res
76
89
  end
77
90
  end
78
91
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: choria-colt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Romuald Conty
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-26 00:00:00.000000000 Z
11
+ date: 2022-11-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: tty-progressbar
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: github_changelog_generator
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -214,6 +228,7 @@ files:
214
228
  - lib/choria/colt/cli/formatter.rb
215
229
  - lib/choria/colt/cli/thor.rb
216
230
  - lib/choria/colt/data_structurer.rb
231
+ - lib/choria/colt/debugger.rb
217
232
  - lib/choria/colt/version.rb
218
233
  - lib/choria/orchestrator.rb
219
234
  - lib/choria/orchestrator/task.rb