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 +4 -4
- data/CHANGELOG.md +9 -0
- data/choria-colt.gemspec +1 -0
- data/lib/choria/colt/cli/formatter.rb +112 -69
- data/lib/choria/colt/cli.rb +43 -3
- data/lib/choria/colt/data_structurer.rb +1 -5
- data/lib/choria/colt/debugger.rb +28 -0
- data/lib/choria/colt/version.rb +1 -1
- data/lib/choria/orchestrator/task/result_set.rb +4 -1
- data/lib/choria/orchestrator/task.rb +16 -3
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5791ce130daf3f0d0300f6ca56fa07f88c6a94e08ff4979d84282526220a3eb4
|
4
|
+
data.tar.gz: d4ce31439d50ba72bff357e13901c4621a1a8f79ce0a8458ed1b0c948e22f7e0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
43
|
+
module FormattedResult
|
44
|
+
attr_accessor :pastel
|
36
45
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
43
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
62
|
-
host = format_host(result, "#{pastel.bright_green '√'} ")
|
63
|
-
headline = "#{pastel.on_green ' '} "
|
78
|
+
private
|
64
79
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
96
|
-
|
97
|
-
].flatten.
|
98
|
-
|
99
|
-
|
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
|
-
|
102
|
-
host = "#{pastel.bright_red '⨯'} #{pastel.host(result.sender)}"
|
103
|
-
headline = "#{pastel.on_red ' '} "
|
130
|
+
headline = "#{pastel.on_red ' '} "
|
104
131
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
|
150
|
+
attr_reader :pastel
|
112
151
|
|
113
|
-
def
|
114
|
-
|
152
|
+
def initialize(colored:)
|
153
|
+
@pastel = Pastel.new(enabled: colored)
|
154
|
+
pastel.alias_color(:host, :cyan)
|
115
155
|
end
|
116
156
|
|
117
|
-
def
|
118
|
-
|
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
|
data/lib/choria/colt/cli.rb
CHANGED
@@ -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.
|
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
|
-
|
94
|
-
|
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
|
data/lib/choria/colt/version.rb
CHANGED
@@ -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
|
-
|
74
|
-
|
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.
|
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-
|
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
|