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.
- checksums.yaml +4 -4
- data/README.md +107 -25
- data/bin/foreman-maintain +1 -0
- data/definitions/checks/disk_speed_minimal.rb +31 -19
- data/definitions/checks/foreman_tasks/invalid/check_old.rb +20 -0
- data/definitions/checks/foreman_tasks/invalid/check_pending_state.rb +20 -0
- data/definitions/checks/foreman_tasks/invalid/check_planning_state.rb +20 -0
- data/definitions/checks/foreman_tasks/not_paused.rb +29 -0
- data/definitions/checks/foreman_tasks/not_running.rb +14 -0
- data/definitions/checks/sync_plans/with_disabled_status.rb +18 -0
- data/definitions/checks/sync_plans/with_enabled_status.rb +18 -0
- data/definitions/checks/system_registration.rb +31 -0
- data/definitions/features/downstream.rb +5 -3
- data/definitions/features/foreman_1_11_x.rb +4 -2
- data/definitions/features/foreman_1_7_x.rb +5 -3
- data/definitions/features/foreman_database.rb +11 -5
- data/definitions/features/foreman_tasks.rb +118 -9
- data/definitions/features/sync_plans.rb +75 -0
- data/definitions/features/upstream.rb +5 -3
- data/definitions/procedures/foreman_tasks/delete.rb +33 -0
- data/definitions/procedures/foreman_tasks/resume.rb +14 -0
- data/definitions/procedures/foreman_tasks/ui_investigate.rb +19 -0
- data/definitions/procedures/hammer_setup.rb +50 -0
- data/definitions/procedures/install_package.rb +17 -0
- data/definitions/procedures/sync_plans/disable.rb +22 -0
- data/definitions/procedures/sync_plans/enable.rb +21 -0
- data/definitions/scenarios/pre_upgrade_check_foreman_1_14.rb +7 -6
- data/definitions/scenarios/pre_upgrade_check_satellite_6_0_z.rb +8 -6
- data/definitions/scenarios/pre_upgrade_check_satellite_6_1.rb +8 -6
- data/definitions/scenarios/pre_upgrade_check_satellite_6_1_z.rb +8 -6
- data/definitions/scenarios/pre_upgrade_check_satellite_6_2.rb +8 -6
- data/definitions/scenarios/pre_upgrade_check_satellite_6_2_z.rb +8 -6
- data/definitions/scenarios/pre_upgrade_check_satellite_6_3.rb +8 -6
- data/lib/foreman_maintain.rb +52 -5
- data/lib/foreman_maintain/check.rb +18 -12
- data/lib/foreman_maintain/cli/base.rb +9 -2
- data/lib/foreman_maintain/cli/health_command.rb +2 -1
- data/lib/foreman_maintain/cli/upgrade_command.rb +2 -0
- data/lib/foreman_maintain/concerns/hammer.rb +20 -0
- data/lib/foreman_maintain/concerns/logger.rb +1 -5
- data/lib/foreman_maintain/concerns/metadata.rb +138 -31
- data/lib/foreman_maintain/concerns/system_helpers.rb +36 -32
- data/lib/foreman_maintain/config.rb +40 -5
- data/lib/foreman_maintain/core_ext.rb +24 -0
- data/lib/foreman_maintain/detector.rb +12 -13
- data/lib/foreman_maintain/error.rb +28 -0
- data/lib/foreman_maintain/executable.rb +86 -11
- data/lib/foreman_maintain/feature.rb +1 -0
- data/lib/foreman_maintain/param.rb +47 -0
- data/lib/foreman_maintain/reporter.rb +20 -3
- data/lib/foreman_maintain/reporter/cli_reporter.rb +166 -66
- data/lib/foreman_maintain/runner.rb +56 -13
- data/lib/foreman_maintain/runner/execution.rb +8 -0
- data/lib/foreman_maintain/scenario.rb +46 -2
- data/lib/foreman_maintain/top_level_modules.rb +3 -0
- data/lib/foreman_maintain/utils.rb +2 -0
- data/lib/foreman_maintain/utils/command_runner.rb +101 -0
- data/lib/foreman_maintain/utils/disk/device.rb +5 -9
- data/lib/foreman_maintain/utils/hammer.rb +78 -0
- data/lib/foreman_maintain/version.rb +1 -1
- data/lib/foreman_maintain/yaml_storage.rb +48 -0
- metadata +27 -9
- data/definitions/checks/foreman_tasks_not_paused.rb +0 -14
- data/definitions/checks/foreman_tasks_not_running.rb +0 -10
- data/definitions/procedures/foreman_tasks_resume.rb +0 -13
- data/lib/foreman_maintain/logger.rb +0 -11
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'csv'
|
3
|
-
require '
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
@input = input
|
22
|
-
@output = output
|
23
|
-
super(generate_message)
|
24
|
-
end
|
18
|
+
def hostname
|
19
|
+
execute('hostname -f')
|
20
|
+
end
|
25
21
|
|
26
|
-
|
27
|
-
|
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 =
|
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,
|
71
|
-
|
72
|
-
if
|
73
|
-
|
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
|
81
|
+
raise command_runner.execution_error
|
76
82
|
end
|
77
83
|
end
|
78
84
|
|
79
|
-
def execute(command,
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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 :
|
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
|
-
@
|
7
|
-
|
8
|
-
@
|
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 =
|
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 =
|
45
|
+
@available_procedures = find_present_classes(Procedure)
|
46
46
|
end
|
47
47
|
filter(@available_procedures, filter_conditions)
|
48
48
|
end
|
49
49
|
|
50
|
-
def
|
50
|
+
def find_present_classes(object_base_class)
|
51
51
|
object_base_class.all_sub_classes.reduce([]) do |array, object_class|
|
52
|
-
|
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
|
-
|
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
|
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
|
-
|
111
|
-
return unless
|
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.
|
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
|
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(
|
6
|
-
@
|
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
|
-
|
26
|
-
|
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
|
-
|
91
|
+
execution.update(message)
|
36
92
|
end
|
37
93
|
|
38
94
|
# internal method called by executor
|
39
95
|
def __run__(execution)
|
40
|
-
|
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
|
@@ -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
|