foreman_maintain 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +193 -0
  4. data/bin/foreman-maintain +9 -0
  5. data/definitions/checks/foreman_tasks_not_paused.rb +14 -0
  6. data/definitions/checks/foreman_tasks_not_running.rb +10 -0
  7. data/definitions/features/downstream.rb +17 -0
  8. data/definitions/features/foreman_1_11_x.rb +7 -0
  9. data/definitions/features/foreman_1_7_x.rb +7 -0
  10. data/definitions/features/foreman_database.rb +15 -0
  11. data/definitions/features/foreman_tasks.rb +22 -0
  12. data/definitions/features/upstream.rb +7 -0
  13. data/definitions/procedures/foreman_tasks_resume.rb +13 -0
  14. data/definitions/scenarios/pre_upgrade_check_foreman_1_14.rb +12 -0
  15. data/definitions/scenarios/pre_upgrade_check_satellite_6_0_z.rb +12 -0
  16. data/definitions/scenarios/pre_upgrade_check_satellite_6_1.rb +12 -0
  17. data/definitions/scenarios/pre_upgrade_check_satellite_6_1_z.rb +12 -0
  18. data/definitions/scenarios/pre_upgrade_check_satellite_6_2.rb +12 -0
  19. data/definitions/scenarios/pre_upgrade_check_satellite_6_2_z.rb +12 -0
  20. data/definitions/scenarios/pre_upgrade_check_satellite_6_3.rb +12 -0
  21. data/lib/foreman_maintain.rb +54 -0
  22. data/lib/foreman_maintain/check.rb +34 -0
  23. data/lib/foreman_maintain/cli.rb +14 -0
  24. data/lib/foreman_maintain/cli/base.rb +47 -0
  25. data/lib/foreman_maintain/cli/health_command.rb +39 -0
  26. data/lib/foreman_maintain/cli/upgrade_command.rb +57 -0
  27. data/lib/foreman_maintain/concerns/finders.rb +69 -0
  28. data/lib/foreman_maintain/concerns/logger.rb +13 -0
  29. data/lib/foreman_maintain/concerns/metadata.rb +124 -0
  30. data/lib/foreman_maintain/concerns/system_helpers.rb +93 -0
  31. data/lib/foreman_maintain/config.rb +17 -0
  32. data/lib/foreman_maintain/detector.rb +146 -0
  33. data/lib/foreman_maintain/executable.rb +44 -0
  34. data/lib/foreman_maintain/feature.rb +22 -0
  35. data/lib/foreman_maintain/logger.rb +11 -0
  36. data/lib/foreman_maintain/procedure.rb +8 -0
  37. data/lib/foreman_maintain/reporter.rb +18 -0
  38. data/lib/foreman_maintain/reporter/cli_reporter.rb +177 -0
  39. data/lib/foreman_maintain/runner.rb +39 -0
  40. data/lib/foreman_maintain/runner/execution.rb +74 -0
  41. data/lib/foreman_maintain/scenario.rb +46 -0
  42. data/lib/foreman_maintain/top_level_modules.rb +13 -0
  43. data/lib/foreman_maintain/version.rb +3 -0
  44. metadata +173 -0
@@ -0,0 +1,17 @@
1
+ module ForemanMaintain
2
+ class Config
3
+ attr_accessor :definitions_dirs, :log_level
4
+
5
+ def initialize(options = {})
6
+ @definitions_dirs = options.fetch(:definitions_dirs,
7
+ [File.join(source_path, 'definitions')])
8
+ @log_level = options.fetch(:log_level, ::Logger::ERROR)
9
+ end
10
+
11
+ private
12
+
13
+ def source_path
14
+ File.expand_path('../../..', __FILE__)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,146 @@
1
+ module ForemanMaintain
2
+ class Detector
3
+ include Concerns::Logger
4
+
5
+ def initialize
6
+ refresh
7
+ end
8
+
9
+ # Returns instance of feature detected on system by label
10
+ def feature(label)
11
+ detect_feature(label)
12
+ end
13
+
14
+ def filter(collection, conditions)
15
+ if conditions
16
+ collection = collection.find_all { |object| match_object?(object, conditions) }
17
+ end
18
+ sort(collection)
19
+ end
20
+
21
+ def refresh
22
+ @features_by_label = {}
23
+ @available_features = []
24
+ @all_features_scanned = false
25
+ @available_checks = nil
26
+ @available_scenarios = nil
27
+ end
28
+
29
+ def available_features(filter_conditions = nil)
30
+ ensure_features_detected
31
+ filter(@available_features, filter_conditions)
32
+ end
33
+
34
+ def available_checks(filter_conditions = nil)
35
+ unless @available_checks
36
+ ensure_features_detected
37
+ @available_checks = find_present_objects(Check)
38
+ end
39
+ filter(@available_checks, filter_conditions)
40
+ end
41
+
42
+ def available_procedures(filter_conditions = nil)
43
+ unless @available_procedures
44
+ ensure_features_detected
45
+ @available_procedures = find_present_objects(Procedure)
46
+ end
47
+ filter(@available_procedures, filter_conditions)
48
+ end
49
+
50
+ def find_present_objects(object_base_class)
51
+ object_base_class.all_sub_classes.reduce([]) do |array, object_class|
52
+ feature_label = object_class.metadata[:for_feature]
53
+ object = object_class.new(feature_label && feature(feature_label))
54
+ array << object if object.present?
55
+ array
56
+ end
57
+ end
58
+
59
+ def available_scenarios(filter_conditions = nil)
60
+ unless @available_scenarios
61
+ ensure_features_detected
62
+ @available_scenarios = Scenario.all_sub_classes.select(&:autodetect?).
63
+ map(&:new).select(&:present?)
64
+ end
65
+ filter(@available_scenarios, filter_conditions)
66
+ end
67
+
68
+ def ensure_features_detected
69
+ return if @all_features_scanned
70
+ @available_features = []
71
+ @features_by_label = {}
72
+ autodetect_features.keys.each do |label|
73
+ detect_feature(label)
74
+ end
75
+ @all_features_scanned = true
76
+ end
77
+
78
+ private
79
+
80
+ def sort(collection)
81
+ collection.sort_by { |item| item.label.to_s }
82
+ end
83
+
84
+ def match_object?(object, conditions)
85
+ conditions = normalize_filter_conditions(conditions)
86
+ return false if conditions[:label] && object.label != conditions[:label]
87
+ return false if conditions[:class] && object.class != conditions[:class]
88
+ conditions[:tags].all? { |tag| object.metadata[:tags].include?(tag) }
89
+ end
90
+
91
+ # rubocop:disable Metrics/MethodLength
92
+ def normalize_filter_conditions(conditions)
93
+ ret = conditions.is_a?(Hash) ? conditions.dup : {}
94
+ ret[:tags] = case conditions
95
+ when Symbol
96
+ [conditions]
97
+ when Array
98
+ conditions
99
+ when Hash
100
+ ret[:tags]
101
+ end
102
+ ret[:tags] = Array(ret.fetch(:tags, []))
103
+ ret
104
+ end
105
+
106
+ # rubocop:disable Metrics/AbcSize
107
+ def detect_feature(label)
108
+ return @features_by_label[label] if @features_by_label.key?(label)
109
+ return unless autodetect_features.key?(label)
110
+ present_feature = autodetect_features[label].find(&:present?)
111
+ return unless present_feature
112
+ @available_features << present_feature
113
+ # we don't allow duplicities of features that are autodetected
114
+ add_feature_by_label(label, present_feature, false)
115
+ additional_features = present_feature.additional_features
116
+ @available_features.concat(additional_features)
117
+ additional_features.each do |feature|
118
+ # we allow duplicities if the feature is added via additional_features
119
+ add_feature_by_label(feature.metadata[:label], feature, true)
120
+ end
121
+ end
122
+
123
+ def autodetect_features
124
+ @autodetect_features ||= Feature.sub_classes.reduce({}) do |hash, feature_class|
125
+ hash.update(feature_class.metadata[:label] =>
126
+ feature_class.all_sub_classes.select(&:autodetect?).reverse.map(&:new))
127
+ end
128
+ end
129
+
130
+ def add_feature_by_label(feature_label, feature, allow_duplicities)
131
+ if @features_by_label.key?(feature_label)
132
+ if allow_duplicities
133
+ unless @features_by_label[feature_label].is_a? Array
134
+ @features_by_label[feature_label] = Array(@features_by_label[feature_label])
135
+ end
136
+ @features_by_label[feature_label] << feature
137
+ else
138
+ raise "Double detection of feature with the same label #{feature_label}:" \
139
+ "#{feature.inspect} vs. #{@features_by_label[feature_label].inspect}"
140
+ end
141
+ else
142
+ @features_by_label[feature_label] = feature
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,44 @@
1
+ module ForemanMaintain
2
+ class Executable
3
+ attr_accessor :associated_feature
4
+
5
+ def initialize(associated_feature)
6
+ @associated_feature = associated_feature
7
+ end
8
+
9
+ # public method to be overriden
10
+ def run
11
+ raise NotImplementedError
12
+ end
13
+
14
+ # override to offer steps to be executed after this one
15
+ def next_steps; end
16
+
17
+ def execution
18
+ if @_execution
19
+ @_execution
20
+ else
21
+ raise 'Trying to get execution information before the run started happened'
22
+ end
23
+ end
24
+
25
+ def success?
26
+ execution.success?
27
+ end
28
+
29
+ def fail?
30
+ execution.fail?
31
+ end
32
+
33
+ # update reporter about the current message
34
+ def say(message)
35
+ @_execution.update(message)
36
+ end
37
+
38
+ # internal method called by executor
39
+ def __run__(execution)
40
+ @_execution = execution
41
+ run
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,22 @@
1
+ module ForemanMaintain
2
+ class Feature
3
+ include Concerns::Logger
4
+ include Concerns::SystemHelpers
5
+ include Concerns::Metadata
6
+ include Concerns::Finders
7
+
8
+ def self.inspect
9
+ "Feature Class #{metadata[:label]}<#{name}>"
10
+ end
11
+
12
+ def inspect
13
+ "#{self.class.metadata[:label]}<#{self.class.name}>"
14
+ end
15
+
16
+ # Override to generate additional feature instances that can't be
17
+ # autodetected directly
18
+ def additional_features
19
+ []
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ require 'logger'
2
+
3
+ module ForemanMaintain
4
+ module Logger
5
+ def logger
6
+ @logger ||= ::Logger.new($stderr).tap do |logger|
7
+ logger.level = ForemanMaintain.config.log_level
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ module ForemanMaintain
2
+ class Procedure < Executable
3
+ include Concerns::Logger
4
+ include Concerns::SystemHelpers
5
+ include Concerns::Metadata
6
+ include Concerns::Finders
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ module ForemanMaintain
2
+ class Reporter
3
+ require 'foreman_maintain/reporter/cli_reporter'
4
+
5
+ # Each public method is a hook called by executor at the specific point
6
+ def before_scenario_starts(_scenario); end
7
+
8
+ def before_execution_starts(_execution); end
9
+
10
+ def on_execution_update(_execution, _update); end
11
+
12
+ def after_execution_finishes(_execution); end
13
+
14
+ def after_scenario_finishes(_scenario); end
15
+
16
+ def on_next_steps_offer(_runner, _steps); end
17
+ end
18
+ end
@@ -0,0 +1,177 @@
1
+ require 'thread'
2
+ require 'highline'
3
+
4
+ module ForemanMaintain
5
+ class Reporter
6
+ class CLIReporter < Reporter
7
+ # Simple spinner able to keep updating current line
8
+ class Spinner
9
+ def initialize(reporter, interval = 0.1)
10
+ @reporter = reporter
11
+ @mutex = Mutex.new
12
+ @active = false
13
+ @interval = interval
14
+ @spinner_index = 0
15
+ @spinner_chars = %w(| / - \\)
16
+ @current_line = ''
17
+ @puts_needed = false
18
+ start_spinner
19
+ end
20
+
21
+ def update(line)
22
+ @mutex.synchronize { @current_line = line }
23
+ end
24
+
25
+ def activate
26
+ @mutex.synchronize { @active = true }
27
+ spin
28
+ end
29
+
30
+ def deactivate
31
+ @mutex.synchronize do
32
+ @active = false
33
+ @reporter.print "\r"
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def start_spinner
40
+ @thread = Thread.new do
41
+ loop do
42
+ spin
43
+ sleep @interval
44
+ end
45
+ end
46
+ end
47
+
48
+ def spin
49
+ @mutex.synchronize do
50
+ return unless @active
51
+ @reporter.clear_line
52
+ @reporter.print "\r"
53
+ line = "#{@spinner_chars[@spinner_index]} #{@current_line}"
54
+ @reporter.print(line)
55
+ @spinner_index = (@spinner_index + 1) % @spinner_chars.size
56
+ end
57
+ end
58
+ end
59
+
60
+ def initialize(stdout = STDOUT, stdin = STDIN)
61
+ @stdout = stdout
62
+ @stdin = stdin
63
+ @hl = HighLine.new
64
+ @max_length = 80
65
+ @line_char = '-'
66
+ @cell_char = '|'
67
+ @spinner = Spinner.new(self)
68
+ end
69
+
70
+ def before_scenario_starts(scenario)
71
+ puts "Running #{scenario.description || scenario.class}"
72
+ hline
73
+ end
74
+
75
+ def before_execution_starts(execution)
76
+ @spinner.update(execution_info(execution, 'running'))
77
+ @spinner.activate
78
+ end
79
+
80
+ def on_execution_update(execution, update)
81
+ @spinner.update(execution_info(execution, update))
82
+ end
83
+
84
+ def after_execution_finishes(execution)
85
+ @spinner.deactivate
86
+ cell(execution_info(execution, status_label(execution, 11), @max_length - 15))
87
+ cell(execution.output) if execution.fail?
88
+ hline
89
+ end
90
+
91
+ def after_scenario_finishes(_scenario); end
92
+
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)
102
+ end
103
+
104
+ def multiple_steps_selection(steps)
105
+ puts 'There are multiple steps to proceed:'
106
+ steps.each_with_index do |step, index|
107
+ puts "#{index + 1}) #{step.description}"
108
+ end
109
+ ask_to_select('Select step to continue', steps, &:description)
110
+ end
111
+
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
120
+ end
121
+ ensure
122
+ clear_line
123
+ end
124
+
125
+ 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]
133
+ end
134
+ ensure
135
+ clear_line
136
+ end
137
+
138
+ def clear_line
139
+ print "\r" + ' ' * @max_length + "\r"
140
+ end
141
+
142
+ def execution_info(execution, text, ljust = nil)
143
+ prefix = "#{execution.name}:"
144
+ prefix = prefix.ljust(ljust) if ljust
145
+ "#{prefix} #{text}"
146
+ end
147
+
148
+ def status_label(execution, ljust)
149
+ mapping = { :success => { :label => '[OK]', :color => :green },
150
+ :fail => { :label => '[FAIL]', :color => :red },
151
+ :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
159
+ end
160
+
161
+ def cell(content)
162
+ print "#{@cell_char} #{content}".ljust(@max_length - 1)
163
+ puts @cell_char
164
+ end
165
+
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
174
+ end
175
+ end
176
+ end
177
+ end