foreman_maintain 0.0.2 → 0.0.3

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +107 -25
  3. data/bin/foreman-maintain +1 -0
  4. data/definitions/checks/disk_speed_minimal.rb +31 -19
  5. data/definitions/checks/foreman_tasks/invalid/check_old.rb +20 -0
  6. data/definitions/checks/foreman_tasks/invalid/check_pending_state.rb +20 -0
  7. data/definitions/checks/foreman_tasks/invalid/check_planning_state.rb +20 -0
  8. data/definitions/checks/foreman_tasks/not_paused.rb +29 -0
  9. data/definitions/checks/foreman_tasks/not_running.rb +14 -0
  10. data/definitions/checks/sync_plans/with_disabled_status.rb +18 -0
  11. data/definitions/checks/sync_plans/with_enabled_status.rb +18 -0
  12. data/definitions/checks/system_registration.rb +31 -0
  13. data/definitions/features/downstream.rb +5 -3
  14. data/definitions/features/foreman_1_11_x.rb +4 -2
  15. data/definitions/features/foreman_1_7_x.rb +5 -3
  16. data/definitions/features/foreman_database.rb +11 -5
  17. data/definitions/features/foreman_tasks.rb +118 -9
  18. data/definitions/features/sync_plans.rb +75 -0
  19. data/definitions/features/upstream.rb +5 -3
  20. data/definitions/procedures/foreman_tasks/delete.rb +33 -0
  21. data/definitions/procedures/foreman_tasks/resume.rb +14 -0
  22. data/definitions/procedures/foreman_tasks/ui_investigate.rb +19 -0
  23. data/definitions/procedures/hammer_setup.rb +50 -0
  24. data/definitions/procedures/install_package.rb +17 -0
  25. data/definitions/procedures/sync_plans/disable.rb +22 -0
  26. data/definitions/procedures/sync_plans/enable.rb +21 -0
  27. data/definitions/scenarios/pre_upgrade_check_foreman_1_14.rb +7 -6
  28. data/definitions/scenarios/pre_upgrade_check_satellite_6_0_z.rb +8 -6
  29. data/definitions/scenarios/pre_upgrade_check_satellite_6_1.rb +8 -6
  30. data/definitions/scenarios/pre_upgrade_check_satellite_6_1_z.rb +8 -6
  31. data/definitions/scenarios/pre_upgrade_check_satellite_6_2.rb +8 -6
  32. data/definitions/scenarios/pre_upgrade_check_satellite_6_2_z.rb +8 -6
  33. data/definitions/scenarios/pre_upgrade_check_satellite_6_3.rb +8 -6
  34. data/lib/foreman_maintain.rb +52 -5
  35. data/lib/foreman_maintain/check.rb +18 -12
  36. data/lib/foreman_maintain/cli/base.rb +9 -2
  37. data/lib/foreman_maintain/cli/health_command.rb +2 -1
  38. data/lib/foreman_maintain/cli/upgrade_command.rb +2 -0
  39. data/lib/foreman_maintain/concerns/hammer.rb +20 -0
  40. data/lib/foreman_maintain/concerns/logger.rb +1 -5
  41. data/lib/foreman_maintain/concerns/metadata.rb +138 -31
  42. data/lib/foreman_maintain/concerns/system_helpers.rb +36 -32
  43. data/lib/foreman_maintain/config.rb +40 -5
  44. data/lib/foreman_maintain/core_ext.rb +24 -0
  45. data/lib/foreman_maintain/detector.rb +12 -13
  46. data/lib/foreman_maintain/error.rb +28 -0
  47. data/lib/foreman_maintain/executable.rb +86 -11
  48. data/lib/foreman_maintain/feature.rb +1 -0
  49. data/lib/foreman_maintain/param.rb +47 -0
  50. data/lib/foreman_maintain/reporter.rb +20 -3
  51. data/lib/foreman_maintain/reporter/cli_reporter.rb +166 -66
  52. data/lib/foreman_maintain/runner.rb +56 -13
  53. data/lib/foreman_maintain/runner/execution.rb +8 -0
  54. data/lib/foreman_maintain/scenario.rb +46 -2
  55. data/lib/foreman_maintain/top_level_modules.rb +3 -0
  56. data/lib/foreman_maintain/utils.rb +2 -0
  57. data/lib/foreman_maintain/utils/command_runner.rb +101 -0
  58. data/lib/foreman_maintain/utils/disk/device.rb +5 -9
  59. data/lib/foreman_maintain/utils/hammer.rb +78 -0
  60. data/lib/foreman_maintain/version.rb +1 -1
  61. data/lib/foreman_maintain/yaml_storage.rb +48 -0
  62. metadata +27 -9
  63. data/definitions/checks/foreman_tasks_not_paused.rb +0 -14
  64. data/definitions/checks/foreman_tasks_not_running.rb +0 -10
  65. data/definitions/procedures/foreman_tasks_resume.rb +0 -13
  66. data/lib/foreman_maintain/logger.rb +0 -11
@@ -1,5 +1,10 @@
1
1
  module ForemanMaintain
2
2
  class Reporter
3
+ class DummySpinner
4
+ def update(_message)
5
+ # do nothing
6
+ end
7
+ end
3
8
  require 'foreman_maintain/reporter/cli_reporter'
4
9
 
5
10
  # Each public method is a hook called by executor at the specific point
@@ -7,12 +12,24 @@ module ForemanMaintain
7
12
 
8
13
  def before_execution_starts(_execution); end
9
14
 
10
- def on_execution_update(_execution, _update); end
11
-
12
15
  def after_execution_finishes(_execution); end
13
16
 
14
17
  def after_scenario_finishes(_scenario); end
15
18
 
16
- def on_next_steps_offer(_runner, _steps); end
19
+ def on_next_steps(_steps); end
20
+
21
+ def with_spinner(_message, &_block)
22
+ yield DummySpinner.new
23
+ end
24
+
25
+ def print(_message); end
26
+
27
+ def puts(_message); end
28
+
29
+ def ask(_message); end
30
+
31
+ def assumeyes?
32
+ false
33
+ end
17
34
  end
18
35
  end
@@ -4,6 +4,12 @@ require 'highline'
4
4
  module ForemanMaintain
5
5
  class Reporter
6
6
  class CLIReporter < Reporter
7
+ DECISION_MAPPER = {
8
+ %w[y yes] => :yes,
9
+ %w[n next no] => :no,
10
+ %w[q quit] => :quit
11
+ }.freeze
12
+
7
13
  # Simple spinner able to keep updating current line
8
14
  class Spinner
9
15
  def initialize(reporter, interval = 0.1)
@@ -12,14 +18,21 @@ module ForemanMaintain
12
18
  @active = false
13
19
  @interval = interval
14
20
  @spinner_index = 0
15
- @spinner_chars = %w(| / - \\)
21
+ @spinner_chars = %w[| / - \\]
16
22
  @current_line = ''
17
23
  @puts_needed = false
18
24
  start_spinner
19
25
  end
20
26
 
21
27
  def update(line)
22
- @mutex.synchronize { @current_line = line }
28
+ @mutex.synchronize do
29
+ @current_line = line
30
+ print_current_line
31
+ end
32
+ end
33
+
34
+ def active?
35
+ @mutex.synchronize { @active }
23
36
  end
24
37
 
25
38
  def activate
@@ -28,9 +41,9 @@ module ForemanMaintain
28
41
  end
29
42
 
30
43
  def deactivate
44
+ return unless active?
31
45
  @mutex.synchronize do
32
46
  @active = false
33
- @reporter.print "\r"
34
47
  end
35
48
  end
36
49
 
@@ -48,60 +61,127 @@ module ForemanMaintain
48
61
  def spin
49
62
  @mutex.synchronize do
50
63
  return unless @active
51
- @reporter.clear_line
52
- @reporter.print "\r"
53
- line = "#{@spinner_chars[@spinner_index]} #{@current_line}"
54
- @reporter.print(line)
64
+ print_current_line
55
65
  @spinner_index = (@spinner_index + 1) % @spinner_chars.size
56
66
  end
57
67
  end
68
+
69
+ def print_current_line
70
+ @reporter.clear_line
71
+ line = "#{@spinner_chars[@spinner_index]} #{@current_line}"
72
+ @reporter.print(line)
73
+ end
58
74
  end
59
75
 
60
- def initialize(stdout = STDOUT, stdin = STDIN)
76
+ def initialize(stdout = STDOUT, stdin = STDIN, options = {})
61
77
  @stdout = stdout
62
78
  @stdin = stdin
63
- @hl = HighLine.new
79
+ options.validate_options!(:assumeyes)
80
+ @assumeyes = options.fetch(:assumeyes, false)
81
+ @hl = HighLine.new(@stdin, @stdout)
64
82
  @max_length = 80
65
83
  @line_char = '-'
66
84
  @cell_char = '|'
67
85
  @spinner = Spinner.new(self)
86
+ @last_line = ''
68
87
  end
69
88
 
70
89
  def before_scenario_starts(scenario)
71
- puts "Running #{scenario.description || scenario.class}"
72
- hline
90
+ puts "\nRunning #{scenario.description || scenario.class}"
91
+ hline('=')
73
92
  end
74
93
 
75
94
  def before_execution_starts(execution)
76
- @spinner.update(execution_info(execution, 'running'))
77
- @spinner.activate
95
+ puts(execution_info(execution, ''))
78
96
  end
79
97
 
80
- def on_execution_update(execution, update)
81
- @spinner.update(execution_info(execution, update))
98
+ def print(string)
99
+ new_line_if_needed
100
+ @stdout.print(string)
101
+ @stdout.flush
102
+ record_last_line(string)
82
103
  end
83
104
 
84
- def after_execution_finishes(execution)
105
+ def puts(string)
106
+ # we don't print the new line right away, as we want to be able to put
107
+ # the status label at the end of the last line, if possible.
108
+ # Therefore, we just mark that we need to print the new line next time
109
+ # we are printing something.
110
+ new_line_if_needed
111
+ @stdout.print(string)
112
+ @stdout.flush
113
+ @new_line_next_time = true
114
+ record_last_line(string)
115
+ end
116
+
117
+ def ask(message, options = {})
118
+ new_line_if_needed
119
+ options.validate_options!(:password)
120
+ # the answer is confirmed by ENTER which will emit a new line
121
+ @new_line_next_time = false
122
+ @last_line = ''
123
+ # add space at the end as otherwise highline would add new line there :/
124
+ message = "#{message} " unless message =~ /\s\Z/
125
+ answer = @hl.ask(message) { |q| q.echo = false if options[:password] }
126
+ answer.to_s.chomp.downcase if answer
127
+ end
128
+
129
+ def new_line_if_needed
130
+ if @new_line_next_time
131
+ @stdout.print("\n")
132
+ @stdout.flush
133
+ @new_line_next_time = false
134
+ end
135
+ end
136
+
137
+ def with_spinner(message)
138
+ new_line_if_needed
139
+ @spinner.activate
140
+ @spinner.update(message)
141
+ yield @spinner
142
+ ensure
85
143
  @spinner.deactivate
86
- cell(execution_info(execution, status_label(execution, 11), @max_length - 15))
87
- cell(execution.output) if execution.fail?
144
+ @new_line_next_time = true
145
+ end
146
+
147
+ def after_execution_finishes(execution)
148
+ puts_status(execution.status)
149
+ puts(execution.output) unless execution.output.empty?
88
150
  hline
151
+ new_line_if_needed
89
152
  end
90
153
 
91
154
  def after_scenario_finishes(_scenario); end
92
155
 
93
- def on_next_steps(runner, steps)
94
- choice = if steps.size > 1
95
- multiple_steps_selection(steps)
96
- elsif ask_to_confirm("Continue with step [#{steps.first.description}]?")
97
- steps.first
98
- else
99
- :quit
100
- end
101
- choice == :quit ? runner.ask_to_quit : runner.add_step(choice)
156
+ def on_next_steps(steps)
157
+ return if steps.empty?
158
+ if steps.size > 1
159
+ multiple_steps_decision(steps)
160
+ else
161
+ single_step_decision(steps.first)
162
+ end
163
+ end
164
+
165
+ def clear_line
166
+ print "\r" + ' ' * @max_length + "\r"
167
+ end
168
+
169
+ private
170
+
171
+ def assumeyes?
172
+ @assumeyes
102
173
  end
103
174
 
104
- def multiple_steps_selection(steps)
175
+ def single_step_decision(step)
176
+ answer = ask_decision("Continue with step [#{step.description}]?")
177
+ if answer == :yes
178
+ step
179
+ else
180
+ answer
181
+ end
182
+ end
183
+
184
+ def multiple_steps_decision(steps)
105
185
  puts 'There are multiple steps to proceed:'
106
186
  steps.each_with_index do |step, index|
107
187
  puts "#{index + 1}) #{step.description}"
@@ -109,68 +189,88 @@ module ForemanMaintain
109
189
  ask_to_select('Select step to continue', steps, &:description)
110
190
  end
111
191
 
112
- def ask_to_confirm(message)
113
- print "#{message}, [yN]"
114
- answer = @stdin.gets.chomp
115
- case answer
116
- when 'y'
117
- true
118
- when 'n'
119
- false
192
+ def ask_decision(message)
193
+ if assumeyes?
194
+ print("#{message} (assuming yes)")
195
+ return :yes
196
+ end
197
+ until_valid_decision do
198
+ filter_decision(ask("#{message}, [y(yes), n(no), q(quit)]"))
120
199
  end
121
200
  ensure
122
201
  clear_line
123
202
  end
124
203
 
204
+ def filter_decision(answer)
205
+ decision = nil
206
+ DECISION_MAPPER.each do |options, decision_label|
207
+ decision = decision_label if options.include?(answer)
208
+ end
209
+ decision
210
+ end
211
+
212
+ # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
125
213
  def ask_to_select(message, steps)
126
- print "#{message}, (q) for quit"
127
- answer = @stdin.gets.chomp
128
- case answer
129
- when 'q'
130
- :quit
131
- when /^\d+$/
132
- steps[answer.to_i - 1]
214
+ if assumeyes?
215
+ puts('(assuming first option)')
216
+ return steps.first
217
+ end
218
+ until_valid_decision do
219
+ answer = ask("#{message}, [n(next), q(quit)]")
220
+ if answer =~ /^\d+$/ && (answer.to_i - 1) < steps.size
221
+ steps[answer.to_i - 1]
222
+ else
223
+ decision = filter_decision(answer)
224
+ if decision == :yes
225
+ steps.first
226
+ else
227
+ decision
228
+ end
229
+ end
133
230
  end
134
231
  ensure
135
232
  clear_line
136
233
  end
137
234
 
138
- def clear_line
139
- print "\r" + ' ' * @max_length + "\r"
235
+ # loop over the block until it returns some non-false value
236
+ def until_valid_decision
237
+ decision = nil
238
+ decision = yield until decision
239
+ decision
140
240
  end
141
241
 
142
- def execution_info(execution, text, ljust = nil)
242
+ def execution_info(execution, text)
143
243
  prefix = "#{execution.name}:"
144
- prefix = prefix.ljust(ljust) if ljust
145
244
  "#{prefix} #{text}"
146
245
  end
147
246
 
148
- def status_label(execution, ljust)
247
+ def puts_status(status)
248
+ label_offset = 10
249
+ padding = @max_length - @last_line.to_s.size - label_offset
250
+ if padding < 0
251
+ new_line_if_needed
252
+ padding = @max_length - label_offset
253
+ end
254
+ @stdout.print(' ' * padding + status_label(status))
255
+ @new_line_next_time = true
256
+ end
257
+
258
+ def status_label(status)
149
259
  mapping = { :success => { :label => '[OK]', :color => :green },
150
260
  :fail => { :label => '[FAIL]', :color => :red },
151
261
  :running => { :label => '[RUNNING]', :color => :blue },
152
- :skipped => { :label => '[SKIPPED]', :color => :yellow } }
153
- properties = mapping[execution.status]
154
- @hl.color(properties[:label].ljust(ljust), properties[:color], :bold)
155
- end
156
-
157
- def hline
158
- puts @line_char * @max_length
262
+ :skipped => { :label => '[SKIPPED]', :color => :yellow },
263
+ :warning => { :label => '[WARNING]', :color => :yellow } }
264
+ properties = mapping[status]
265
+ @hl.color(properties[:label], properties[:color], :bold)
159
266
  end
160
267
 
161
- def cell(content)
162
- print "#{@cell_char} #{content}".ljust(@max_length - 1)
163
- puts @cell_char
268
+ def hline(line_char = @line_char)
269
+ puts line_char * @max_length
164
270
  end
165
271
 
166
- def print(string)
167
- @stdout.print(string)
168
- @stdout.flush
169
- end
170
-
171
- def puts(string)
172
- @stdout.puts(string)
173
- @stdout.flush
272
+ def record_last_line(string)
273
+ @last_line = string.lines.to_a.last
174
274
  end
175
275
  end
176
276
  end
@@ -2,38 +2,81 @@ module ForemanMaintain
2
2
  # Class responsible for running the scenario
3
3
  class Runner
4
4
  require 'foreman_maintain/runner/execution'
5
- def initialize(reporter, scenario)
5
+ def initialize(reporter, scenarios, options = {})
6
+ options.validate_options!(:assumeyes)
7
+ @assumeyes = options.fetch(:assumeyes, false)
6
8
  @reporter = reporter
7
- @scenario = scenario
8
- @executions = []
9
- @steps_to_run = @scenario.steps.dup
9
+ @scenarios = Array(scenarios)
10
+ @scenarios_with_dependencies = scenarios_with_dependencies
10
11
  @quit = false
11
12
  end
12
13
 
14
+ def assumeyes?
15
+ @assumeyes
16
+ end
17
+
18
+ def scenarios_with_dependencies
19
+ @scenarios.map do |scenario|
20
+ scenario.before_scenarios + [scenario]
21
+ end.flatten
22
+ end
23
+
13
24
  def run
14
- @reporter.before_scenario_starts(@scenario)
25
+ scenarios_with_dependencies.each do |scenario|
26
+ run_scenario(scenario)
27
+ end
28
+ end
29
+
30
+ def run_scenario(scenario)
31
+ @steps_to_run = scenario.steps.dup
32
+ @reporter.before_scenario_starts(scenario)
15
33
  while !@quit && !@steps_to_run.empty?
16
34
  step = @steps_to_run.shift
35
+ @reporter.puts('Rerunning the check after fix procedure') if rerun_check?(step)
17
36
  execution = Execution.new(step, @reporter)
18
37
  execution.run
19
- @executions << execution
20
- ask_about_offered_steps(step.next_steps)
38
+ ask_about_offered_steps(step)
21
39
  end
22
- @reporter.after_scenario_finishes(@scenario)
40
+ @reporter.after_scenario_finishes(scenario)
23
41
  end
24
42
 
25
- def ask_to_quit
43
+ def ask_to_quit(_step = nil)
26
44
  @quit = true
27
45
  end
28
46
 
29
- def add_step(step)
30
- @steps_to_run.unshift(step)
47
+ def add_steps(*steps)
48
+ # we we add the steps at the beginning, but still keeping the
49
+ # order of steps passed in the arguments
50
+ steps.reverse.each do |step|
51
+ @steps_to_run.unshift(step)
52
+ end
31
53
  end
32
54
 
33
55
  private
34
56
 
35
- def ask_about_offered_steps(steps)
36
- @reporter.on_next_steps(self, steps) if steps
57
+ # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity
58
+ def ask_about_offered_steps(step)
59
+ if assumeyes? && rerun_check?(step)
60
+ @reporter.puts 'Check still failing after attempt to fix. Skipping'
61
+ return :no
62
+ end
63
+ if step.next_steps && !step.next_steps.empty?
64
+ @last_decision_step = step
65
+ steps = step.next_steps.map(&:ensure_instance)
66
+ decision = @reporter.on_next_steps(steps)
67
+ case decision
68
+ when :quit
69
+ ask_to_quit
70
+ when Executable
71
+ chosen_steps = [decision]
72
+ chosen_steps << step if step.is_a?(Check)
73
+ add_steps(*chosen_steps)
74
+ end
75
+ end
76
+ end
77
+
78
+ def rerun_check?(step)
79
+ @last_decision_step == step
37
80
  end
38
81
  end
39
82
  end