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,6 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'csv'
3
- require 'English'
3
+ require 'shellwords'
4
4
 
5
5
  module ForemanMaintain
6
6
  module Concerns
@@ -15,28 +15,27 @@ module ForemanMaintain
15
15
  class Version < Gem::Version
16
16
  end
17
17
 
18
- class ExecutionError < StandardError
19
- def initialize(command, input, output)
20
- @command = command
21
- @input = input
22
- @output = output
23
- super(generate_message)
24
- end
18
+ def hostname
19
+ execute('hostname -f')
20
+ end
25
21
 
26
- def generate_message
27
- ret = "Could not execute #{command}"
28
- ret << "with input '#{input}'" if input
29
- ret << ":\n #{output}" if output && !output.empty?
30
- ret
31
- end
22
+ def file_exists?(filename)
23
+ File.exist?(filename)
32
24
  end
33
25
 
34
26
  def version(value)
35
27
  Version.new(value)
36
28
  end
37
29
 
30
+ def install_packages(packages, options = {})
31
+ options.validate_options!(:assumeyes)
32
+ yum_options = []
33
+ yum_options << '-y' if options[:assumeyes]
34
+ execute!("yum #{yum_options.join(' ')} install #{packages.join(' ')}", :interactive => true)
35
+ end
36
+
38
37
  def check_min_version(name, minimal_version)
39
- current_version = rpm_version(name)
38
+ current_version = package_version(name)
40
39
  if current_version
41
40
  return current_version >= version(minimal_version)
42
41
  end
@@ -47,6 +46,11 @@ module ForemanMaintain
47
46
  (execute('rpm -q foreman') =~ /6sat.noarch/)
48
47
  end
49
48
 
49
+ def package_version(name)
50
+ # space for extension to support non-rpm distributions
51
+ rpm_version(name)
52
+ end
53
+
50
54
  def rpm_version(name)
51
55
  rpm_version = execute(%(rpm -q '#{name}' --queryformat="%{VERSION}"))
52
56
  if $CHILD_STATUS.success?
@@ -63,30 +67,30 @@ module ForemanMaintain
63
67
  end
64
68
 
65
69
  def execute?(command, input = nil)
66
- execute(command, input)
70
+ execute(command, :stdin => input)
67
71
  $CHILD_STATUS.success?
68
72
  end
69
73
 
70
- def execute!(command, input = nil)
71
- output = execute(command, input)
72
- if $CHILD_STATUS.success?
73
- output
74
+ def execute!(command, options = {})
75
+ command_runner = Utils::CommandRunner.new(logger, command, options)
76
+ execution.puts '' if command_runner.interactive? && respond_to?(:execution)
77
+ command_runner.run
78
+ if command_runner.success?
79
+ command_runner.output
74
80
  else
75
- raise ExecutionError.new(command, input, output)
81
+ raise command_runner.execution_error
76
82
  end
77
83
  end
78
84
 
79
- def execute(command, stdin = nil)
80
- logger.debug("Running command #{command.inspect} with stdin #{stdin.inspect}")
81
- IO.popen("#{command} 2>&1", 'r+') do |f|
82
- if stdin
83
- f.puts(stdin)
84
- f.close_write
85
- end
86
- output = f.read
87
- logger.debug("output of the command:\n #{output}")
88
- output.strip
89
- end
85
+ def execute(command, options = {})
86
+ command_runner = Utils::CommandRunner.new(logger, command, options)
87
+ execution.puts '' if command_runner.interactive? && respond_to?(:execution)
88
+ command_runner.run
89
+ command_runner.output
90
+ end
91
+
92
+ def shellescape(string)
93
+ Shellwords.escape(string)
90
94
  end
91
95
  end
92
96
  end
@@ -1,17 +1,52 @@
1
+ require 'fileutils'
1
2
  module ForemanMaintain
2
3
  class Config
3
- attr_accessor :definitions_dirs, :log_level
4
+ attr_accessor :pre_setup_log_messages,
5
+ :config_file, :definitions_dirs, :log_level, :log_dir, :storage_file
4
6
 
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)
7
+ def initialize(options)
8
+ @pre_setup_log_messages = []
9
+ @config_file = options.fetch(:config_file, default_config_file)
10
+ @options = load_config
11
+ @definitions_dirs = @options.fetch(:definitions_dirs,
12
+ [File.join(source_path, 'definitions')])
13
+
14
+ @log_level = @options.fetch(:log_level, ::Logger::DEBUG)
15
+ @log_dir = find_log_dir_path(@options.fetch(:log_dir, 'log'))
16
+ @storage_file = @options.fetch(:storage_file, 'data.yml')
9
17
  end
10
18
 
11
19
  private
12
20
 
21
+ def load_config
22
+ if File.exist?(config_file)
23
+ YAML.load(File.open(config_file)) || {}
24
+ else
25
+ @pre_setup_log_messages <<
26
+ "Config file #{config_file} not found, using default configuration"
27
+ {}
28
+ end
29
+ rescue => e
30
+ raise "Couldn't load configuration file. Error: #{e.message}"
31
+ end
32
+
33
+ def default_config_file
34
+ File.join(source_path, 'config/foreman_maintain.yml')
35
+ end
36
+
13
37
  def source_path
14
38
  File.expand_path('../../..', __FILE__)
15
39
  end
40
+
41
+ def find_log_dir_path(log_dir)
42
+ log_dir_path = File.expand_path(log_dir)
43
+ begin
44
+ FileUtils.mkdir_p(log_dir_path, :mode => 0o750) unless File.exist?(log_dir_path)
45
+ rescue => e
46
+ $stderr.puts "No permissions to create log dir #{log_dir}"
47
+ $stderr.puts e.message.inspect
48
+ end
49
+ log_dir_path
50
+ end
16
51
  end
17
52
  end
@@ -0,0 +1,24 @@
1
+ module ForemanMaintain
2
+ module CoreExt
3
+ module StripHeredoc
4
+ def strip_heredoc
5
+ indent = scan(/^[ \t]*(?=\S)/).min.size || 0
6
+ gsub(/^[ \t]{#{indent}}/, '')
7
+ end
8
+ end
9
+ String.send(:include, StripHeredoc)
10
+
11
+ module ValidateOptions
12
+ def validate_options!(*valid_keys)
13
+ valid_keys.flatten!
14
+ unexpected_options = keys - valid_keys - valid_keys.map(&:to_s)
15
+ unless unexpected_options.empty?
16
+ raise ArgumentError, "Unexpected options #{unexpected_options.inspect}. "\
17
+ "Valid keys are: #{valid_keys.map(&:inspect).join(', ')}"
18
+ end
19
+ self
20
+ end
21
+ end
22
+ Hash.send(:include, ValidateOptions)
23
+ end
24
+ end
@@ -34,7 +34,7 @@ module ForemanMaintain
34
34
  def available_checks(filter_conditions = nil)
35
35
  unless @available_checks
36
36
  ensure_features_detected
37
- @available_checks = find_present_objects(Check)
37
+ @available_checks = find_present_classes(Check)
38
38
  end
39
39
  filter(@available_checks, filter_conditions)
40
40
  end
@@ -42,16 +42,14 @@ module ForemanMaintain
42
42
  def available_procedures(filter_conditions = nil)
43
43
  unless @available_procedures
44
44
  ensure_features_detected
45
- @available_procedures = find_present_objects(Procedure)
45
+ @available_procedures = find_present_classes(Procedure)
46
46
  end
47
47
  filter(@available_procedures, filter_conditions)
48
48
  end
49
49
 
50
- def find_present_objects(object_base_class)
50
+ def find_present_classes(object_base_class)
51
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?
52
+ array << object_class if object_class.present?
55
53
  array
56
54
  end
57
55
  end
@@ -60,7 +58,7 @@ module ForemanMaintain
60
58
  unless @available_scenarios
61
59
  ensure_features_detected
62
60
  @available_scenarios = Scenario.all_sub_classes.select(&:autodetect?).
63
- map(&:new).select(&:present?)
61
+ select(&:present?).map(&:new)
64
62
  end
65
63
  filter(@available_scenarios, filter_conditions)
66
64
  end
@@ -84,11 +82,10 @@ module ForemanMaintain
84
82
  def match_object?(object, conditions)
85
83
  conditions = normalize_filter_conditions(conditions)
86
84
  return false if conditions[:label] && object.label != conditions[:label]
87
- return false if conditions[:class] && object.class != conditions[:class]
85
+ return false if conditions[:class] && object != conditions[:class]
88
86
  conditions[:tags].all? { |tag| object.metadata[:tags].include?(tag) }
89
87
  end
90
88
 
91
- # rubocop:disable Metrics/MethodLength
92
89
  def normalize_filter_conditions(conditions)
93
90
  ret = conditions.is_a?(Hash) ? conditions.dup : {}
94
91
  ret[:tags] = case conditions
@@ -107,8 +104,9 @@ module ForemanMaintain
107
104
  def detect_feature(label)
108
105
  return @features_by_label[label] if @features_by_label.key?(label)
109
106
  return unless autodetect_features.key?(label)
110
- present_feature = autodetect_features[label].find(&:present?)
111
- return unless present_feature
107
+ present_feature_class = autodetect_features[label].find(&:present?)
108
+ return unless present_feature_class
109
+ present_feature = present_feature_class.new
112
110
  @available_features << present_feature
113
111
  # we don't allow duplicities of features that are autodetected
114
112
  add_feature_by_label(label, present_feature, false)
@@ -116,14 +114,15 @@ module ForemanMaintain
116
114
  @available_features.concat(additional_features)
117
115
  additional_features.each do |feature|
118
116
  # we allow duplicities if the feature is added via additional_features
119
- add_feature_by_label(feature.metadata[:label], feature, true)
117
+ add_feature_by_label(feature.label, feature, true)
120
118
  end
119
+ present_feature
121
120
  end
122
121
 
123
122
  def autodetect_features
124
123
  @autodetect_features ||= Feature.sub_classes.reduce({}) do |hash, feature_class|
125
124
  hash.update(feature_class.metadata[:label] =>
126
- feature_class.all_sub_classes.select(&:autodetect?).reverse.map(&:new))
125
+ feature_class.all_sub_classes.select(&:autodetect?).reverse)
127
126
  end
128
127
  end
129
128
 
@@ -0,0 +1,28 @@
1
+ module ForemanMaintain
2
+ module Error
3
+ class Fail < StandardError
4
+ end
5
+
6
+ class Warn < StandardError
7
+ end
8
+
9
+ class ExecutionError < StandardError
10
+ attr_reader :command, :input, :output, :exit_status
11
+
12
+ def initialize(command, exit_status, input, output)
13
+ @command = command
14
+ @exit_status = exit_status
15
+ @input = input
16
+ @output = output
17
+ super(generate_message)
18
+ end
19
+
20
+ def generate_message
21
+ ret = "Failed executing #{command}, exit status #{exit_status}"
22
+ ret << "with input '#{input}'" if input
23
+ ret << ":\n #{output}" if output && !output.empty?
24
+ ret
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,9 +1,59 @@
1
1
  module ForemanMaintain
2
2
  class Executable
3
+ extend Forwardable
4
+ attr_reader :options
5
+ def_delegators :execution, :success?, :fail?, :output, :assumeyes?
6
+ def_delegators :execution, :puts, :print, :with_spinner, :ask
7
+
3
8
  attr_accessor :associated_feature
4
9
 
5
- def initialize(associated_feature)
6
- @associated_feature = associated_feature
10
+ def initialize(options = {})
11
+ @options = options.inject({}) { |h, (k, v)| h.update(k.to_s => v) }
12
+ @param_values = {}
13
+ setup_params
14
+ after_initialize
15
+ end
16
+
17
+ # To be able to call uniq on a set of steps to deduplicate the same steps
18
+ # inside the scenario
19
+ def eql?(other)
20
+ self.class.eql?(other.class) && options.eql?(other.options)
21
+ end
22
+
23
+ def hash
24
+ [self.class, options].hash
25
+ end
26
+
27
+ # public method to be overriden to perform after-initialization steps
28
+ def after_initialize; end
29
+
30
+ # processes the params from provided options
31
+ def setup_params
32
+ @options.validate_options!(params.values.map(&:name).map(&:to_s))
33
+ params.values.each do |param|
34
+ set_param_variable(param.name, param.process(@options[param.name.to_s]))
35
+ end
36
+ end
37
+
38
+ def set_param_variable(param_name, value)
39
+ @param_values[param_name] = value
40
+ if instance_variable_defined?("@#{param_name}")
41
+ raise "Instance variable @#{param_name} already set"
42
+ end
43
+ instance_variable_set("@#{param_name}", value)
44
+ end
45
+
46
+ def associated_feature
47
+ return @associated_feature if defined? @associated_feature
48
+ if metadata[:for_feature]
49
+ @associated_feature = feature(metadata[:for_feature])
50
+ end
51
+ end
52
+
53
+ # next steps to be offered to the user after the step is run
54
+ # It can be added for example from the assert method
55
+ def next_steps
56
+ @next_steps ||= []
7
57
  end
8
58
 
9
59
  # public method to be overriden
@@ -11,9 +61,6 @@ module ForemanMaintain
11
61
  raise NotImplementedError
12
62
  end
13
63
 
14
- # override to offer steps to be executed after this one
15
- def next_steps; end
16
-
17
64
  def execution
18
65
  if @_execution
19
66
  @_execution
@@ -22,23 +69,51 @@ module ForemanMaintain
22
69
  end
23
70
  end
24
71
 
25
- def success?
26
- execution.success?
72
+ # public method to be overriden: it can perform additional checks
73
+ # to say, if the step is actually necessary to run. For example an `InstallPackage`
74
+ # procedure would not be necessary when the package is already installed.
75
+ def necessary?
76
+ true
27
77
  end
28
78
 
29
- def fail?
30
- execution.fail?
79
+ def fail!(message)
80
+ execution.status = :fail
81
+ execution.output << message
82
+ end
83
+
84
+ def warn!(message)
85
+ execution.status = :warning
86
+ execution.output << message
31
87
  end
32
88
 
33
89
  # update reporter about the current message
34
90
  def say(message)
35
- @_execution.update(message)
91
+ execution.update(message)
36
92
  end
37
93
 
38
94
  # internal method called by executor
39
95
  def __run__(execution)
40
- @_execution = execution
96
+ setup_execution_state(execution)
41
97
  run
42
98
  end
99
+
100
+ # method defined both on object and class to ensure we work always with object
101
+ # even when the definitions provide us only class
102
+ def ensure_instance
103
+ self
104
+ end
105
+
106
+ # clean the execution-specific state to prepare for the next execution
107
+ # attempts
108
+ def setup_execution_state(execution)
109
+ @_execution = execution
110
+ @next_steps = []
111
+ end
112
+
113
+ class << self
114
+ def ensure_instance
115
+ new
116
+ end
117
+ end
43
118
  end
44
119
  end
@@ -4,6 +4,7 @@ module ForemanMaintain
4
4
  include Concerns::SystemHelpers
5
5
  include Concerns::Metadata
6
6
  include Concerns::Finders
7
+ include ForemanMaintain::Concerns::Hammer
7
8
 
8
9
  def self.inspect
9
10
  "Feature Class #{metadata[:label]}<#{name}>"
@@ -0,0 +1,47 @@
1
+ module ForemanMaintain
2
+ class Param
3
+ attr_reader :name, :description, :options
4
+ def initialize(name, description, options, &block)
5
+ options.validate_options!(:description, :required, :flag, :array)
6
+ @name = name
7
+ @description = description || options[:description] || ''
8
+ @options = options
9
+ @required = @options.fetch(:required, false)
10
+ @flag = @options.fetch(:flag, false)
11
+ @block = block
12
+ @array = @options.fetch(:array, false)
13
+ end
14
+
15
+ def flag?
16
+ @flag
17
+ end
18
+
19
+ def required?
20
+ @required
21
+ end
22
+
23
+ def array?
24
+ @array
25
+ end
26
+
27
+ # rubocop:disable Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity
28
+ def process(value)
29
+ value = process_array(value) if array?
30
+ value = @block.call(value) if @block
31
+ if value.nil? && required?
32
+ raise ArgumentError, "Param #{name} is required but no value given"
33
+ elsif flag?
34
+ value = value ? true : false
35
+ end
36
+ value
37
+ end
38
+
39
+ def process_array(value)
40
+ if value.is_a?(Array)
41
+ value
42
+ else
43
+ value.to_s.split(',').map(&:strip)
44
+ end
45
+ end
46
+ end
47
+ end