choria-colt 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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