foreman_maintain 0.0.1
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 +7 -0
- data/LICENSE +674 -0
- data/README.md +193 -0
- data/bin/foreman-maintain +9 -0
- data/definitions/checks/foreman_tasks_not_paused.rb +14 -0
- data/definitions/checks/foreman_tasks_not_running.rb +10 -0
- data/definitions/features/downstream.rb +17 -0
- data/definitions/features/foreman_1_11_x.rb +7 -0
- data/definitions/features/foreman_1_7_x.rb +7 -0
- data/definitions/features/foreman_database.rb +15 -0
- data/definitions/features/foreman_tasks.rb +22 -0
- data/definitions/features/upstream.rb +7 -0
- data/definitions/procedures/foreman_tasks_resume.rb +13 -0
- data/definitions/scenarios/pre_upgrade_check_foreman_1_14.rb +12 -0
- data/definitions/scenarios/pre_upgrade_check_satellite_6_0_z.rb +12 -0
- data/definitions/scenarios/pre_upgrade_check_satellite_6_1.rb +12 -0
- data/definitions/scenarios/pre_upgrade_check_satellite_6_1_z.rb +12 -0
- data/definitions/scenarios/pre_upgrade_check_satellite_6_2.rb +12 -0
- data/definitions/scenarios/pre_upgrade_check_satellite_6_2_z.rb +12 -0
- data/definitions/scenarios/pre_upgrade_check_satellite_6_3.rb +12 -0
- data/lib/foreman_maintain.rb +54 -0
- data/lib/foreman_maintain/check.rb +34 -0
- data/lib/foreman_maintain/cli.rb +14 -0
- data/lib/foreman_maintain/cli/base.rb +47 -0
- data/lib/foreman_maintain/cli/health_command.rb +39 -0
- data/lib/foreman_maintain/cli/upgrade_command.rb +57 -0
- data/lib/foreman_maintain/concerns/finders.rb +69 -0
- data/lib/foreman_maintain/concerns/logger.rb +13 -0
- data/lib/foreman_maintain/concerns/metadata.rb +124 -0
- data/lib/foreman_maintain/concerns/system_helpers.rb +93 -0
- data/lib/foreman_maintain/config.rb +17 -0
- data/lib/foreman_maintain/detector.rb +146 -0
- data/lib/foreman_maintain/executable.rb +44 -0
- data/lib/foreman_maintain/feature.rb +22 -0
- data/lib/foreman_maintain/logger.rb +11 -0
- data/lib/foreman_maintain/procedure.rb +8 -0
- data/lib/foreman_maintain/reporter.rb +18 -0
- data/lib/foreman_maintain/reporter/cli_reporter.rb +177 -0
- data/lib/foreman_maintain/runner.rb +39 -0
- data/lib/foreman_maintain/runner/execution.rb +74 -0
- data/lib/foreman_maintain/scenario.rb +46 -0
- data/lib/foreman_maintain/top_level_modules.rb +13 -0
- data/lib/foreman_maintain/version.rb +3 -0
- metadata +173 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module ForemanMaintain
|
2
|
+
class Check < Executable
|
3
|
+
include Concerns::Logger
|
4
|
+
include Concerns::SystemHelpers
|
5
|
+
include Concerns::Metadata
|
6
|
+
include Concerns::Finders
|
7
|
+
|
8
|
+
attr_accessor :associated_feature
|
9
|
+
|
10
|
+
class Fail < StandardError
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(associated_feature)
|
14
|
+
@associated_feature = associated_feature
|
15
|
+
end
|
16
|
+
|
17
|
+
def assert(condition, error_message)
|
18
|
+
raise Fail, error_message unless condition
|
19
|
+
end
|
20
|
+
|
21
|
+
# public method to be overriden
|
22
|
+
def run
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
# internal method called by executor
|
27
|
+
def __run__(execution)
|
28
|
+
super
|
29
|
+
rescue Fail => e
|
30
|
+
execution.status = :fail
|
31
|
+
execution.output << e.message
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'clamp'
|
2
|
+
require 'highline'
|
3
|
+
require 'foreman_maintain/cli/base'
|
4
|
+
require 'foreman_maintain/cli/health_command'
|
5
|
+
require 'foreman_maintain/cli/upgrade_command'
|
6
|
+
|
7
|
+
module ForemanMaintain
|
8
|
+
module Cli
|
9
|
+
class MainCommand < Base
|
10
|
+
subcommand 'health', 'Health related commands', HealthCommand
|
11
|
+
subcommand 'upgrade', 'Upgrade related commands', UpgradeCommand
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module ForemanMaintain
|
2
|
+
module Cli
|
3
|
+
class Base < Clamp::Command
|
4
|
+
include Concerns::Finders
|
5
|
+
|
6
|
+
def dashize(string)
|
7
|
+
string.to_s.tr('_', '-')
|
8
|
+
end
|
9
|
+
|
10
|
+
def underscorize(string)
|
11
|
+
string.to_s.tr('-', '_')
|
12
|
+
end
|
13
|
+
|
14
|
+
def label_string(string)
|
15
|
+
HighLine.color("[#{dashize(string)}]", :yellow)
|
16
|
+
end
|
17
|
+
|
18
|
+
def tag_string(string)
|
19
|
+
HighLine.color("[#{dashize(string)}]", :cyan)
|
20
|
+
end
|
21
|
+
|
22
|
+
def reporter
|
23
|
+
@reporter ||= ForemanMaintain::Reporter::CLIReporter.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def run_scenario(scenario)
|
27
|
+
ForemanMaintain::Runner.new(reporter, scenario).run
|
28
|
+
end
|
29
|
+
|
30
|
+
def available_checks
|
31
|
+
filter = {}
|
32
|
+
filter[:tags] = tags if respond_to?(:tags)
|
33
|
+
ForemanMaintain.available_checks(filter)
|
34
|
+
end
|
35
|
+
|
36
|
+
def available_tags(collection)
|
37
|
+
collection.inject([]) { |array, check| array.concat(check.tags).uniq }.sort_by(&:to_s)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.tags_option
|
41
|
+
option '--tags', 'tags', 'Limit only for specific set of tags' do |tags|
|
42
|
+
tags.split(',').map(&:strip).map { |tag| underscorize(tag).to_sym }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ForemanMaintain
|
2
|
+
module Cli
|
3
|
+
class HealthCommand < Base
|
4
|
+
subcommand 'list-checks', 'List the checks based on criteria' do
|
5
|
+
tags_option
|
6
|
+
|
7
|
+
def execute
|
8
|
+
available_checks.each { |check| print_check_info(check) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def print_check_info(check)
|
12
|
+
desc = "#{label_string(check.label)} #{check.description}".ljust(80)
|
13
|
+
tags = check.tags.map { |t| tag_string(t) }.join(' ').to_s
|
14
|
+
puts "#{desc} #{tags}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
subcommand 'list-tags', 'List the tags to use for filtering checks' do
|
19
|
+
def execute
|
20
|
+
available_tags(available_checks).each { |tag| puts tag_string(tag) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def print_check_info(check)
|
24
|
+
desc = "#{label_string(check.label)} #{check.description}".ljust(80)
|
25
|
+
tags = check.tags.map { |t| tag_string(t) }.join(' ').to_s
|
26
|
+
puts "#{desc} #{tags}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
subcommand 'check', 'Run the health checks against the system' do
|
31
|
+
tags_option
|
32
|
+
|
33
|
+
def execute
|
34
|
+
run_scenario(Scenario::ChecksScenario.new(tags || [:basic]))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module ForemanMaintain
|
2
|
+
module Cli
|
3
|
+
class UpgradeCommand < Base
|
4
|
+
def tags_to_versions
|
5
|
+
{ :satellite_6_0_z => '6.0.z',
|
6
|
+
:satellite_6_1 => '6.1',
|
7
|
+
:satellite_6_1_z => '6.1.z',
|
8
|
+
:satellite_6_2 => '6.2',
|
9
|
+
:satellite_6_2_z => '6.2.z',
|
10
|
+
:satellite_6_3 => '6.3' }
|
11
|
+
end
|
12
|
+
|
13
|
+
# We search for scenarios available for the system and determine
|
14
|
+
# user-friendly version numbers for it.
|
15
|
+
# This method returns a hash of mapping the versions to scenarios to run
|
16
|
+
# The tag is determining which kind of scenario we're searching for
|
17
|
+
# (such as pre_upgrade_check)
|
18
|
+
def available_target_versions(tag)
|
19
|
+
conditions = { :tags => [tag] }
|
20
|
+
find_scenarios(conditions).inject({}) do |hash, scenario|
|
21
|
+
# find tag that represent the version upgrade
|
22
|
+
version_tag = scenario.tags.find { |t| tags_to_versions.key?(t) }
|
23
|
+
if version_tag
|
24
|
+
hash.update(tags_to_versions[version_tag] => scenario)
|
25
|
+
else
|
26
|
+
hash
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def print_versions(target_versions)
|
32
|
+
target_versions.keys.sort.each { |version| puts version }
|
33
|
+
end
|
34
|
+
|
35
|
+
subcommand 'list-versions', 'List versions this system is upgradable to' do
|
36
|
+
def execute
|
37
|
+
print_versions(available_target_versions(:pre_upgrade_check))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
subcommand 'check', 'Run pre-upgrade checks for upgradeing to specified version' do
|
42
|
+
parameter 'TARGET_VERSION', 'Target version of the upgrade', :required => false
|
43
|
+
def execute
|
44
|
+
versions_to_scenarios = available_target_versions(:pre_upgrade_check)
|
45
|
+
scenario = versions_to_scenarios[target_version]
|
46
|
+
if scenario
|
47
|
+
run_scenario(scenario)
|
48
|
+
else
|
49
|
+
puts "The specified version #{target_version} is unavailable"
|
50
|
+
puts 'Possible target versions are:'
|
51
|
+
print_versions(versions_to_scenarios)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ForemanMaintain
|
2
|
+
module Concerns
|
3
|
+
module Finders
|
4
|
+
def detector
|
5
|
+
@detector ||= ForemanMaintain.detector
|
6
|
+
end
|
7
|
+
|
8
|
+
def feature(label)
|
9
|
+
detector.feature(label)
|
10
|
+
end
|
11
|
+
|
12
|
+
def check(label)
|
13
|
+
ensure_one_object(:check, label)
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_checks(conditions)
|
17
|
+
detector.available_checks(conditions)
|
18
|
+
end
|
19
|
+
|
20
|
+
def procedure(label)
|
21
|
+
ensure_one_object(:procedure, label)
|
22
|
+
end
|
23
|
+
|
24
|
+
def find_procedures(conditions)
|
25
|
+
detector.available_procedures(conditions)
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_scenarios(conditions)
|
29
|
+
detector.available_scenarios(conditions)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def ensure_one_object(object_type, label_or_class)
|
35
|
+
objects = find_objects(object_type, label_or_class)
|
36
|
+
if objects.first.nil?
|
37
|
+
raise "#{object_type} #{label_or_class} not present"
|
38
|
+
elsif objects.size > 1
|
39
|
+
raise "Multiple objects of #{object_type} found for #{label_or_class}"
|
40
|
+
else
|
41
|
+
objects.first
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def label_or_class_condition(label_or_class)
|
46
|
+
case label_or_class
|
47
|
+
when Symbol
|
48
|
+
{ :label => label_or_class }
|
49
|
+
when Class
|
50
|
+
{ :class => label_or_class }
|
51
|
+
else
|
52
|
+
raise 'Expecting symbol or class'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_objects(object_type, label_or_class)
|
57
|
+
conditions = label_or_class_condition(label_or_class)
|
58
|
+
case object_type
|
59
|
+
when :procedure
|
60
|
+
detector.available_procedures(conditions)
|
61
|
+
when :check
|
62
|
+
detector.available_checks(conditions)
|
63
|
+
else
|
64
|
+
raise "Unexpected object type #{object_type}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module ForemanMaintain
|
2
|
+
module Concerns
|
3
|
+
module Metadata
|
4
|
+
module DSL
|
5
|
+
def label(label)
|
6
|
+
metadata[:label] = label
|
7
|
+
end
|
8
|
+
|
9
|
+
def tags(*tags)
|
10
|
+
metadata[:tags].concat(tags)
|
11
|
+
end
|
12
|
+
|
13
|
+
def description(description)
|
14
|
+
metadata[:description] = description
|
15
|
+
end
|
16
|
+
|
17
|
+
def confine(&block)
|
18
|
+
metadata[:confine_blocks] << block
|
19
|
+
end
|
20
|
+
|
21
|
+
# Mark the class as manual: this means the instance
|
22
|
+
# of class will not be initialized by detector to check the confine block
|
23
|
+
# to determine if it's valid on the system or not.
|
24
|
+
# The classes marked for manual detect need to be initialized
|
25
|
+
# in from other places (such as `additional_features` in Feature)
|
26
|
+
def manual_detection
|
27
|
+
@autodetect = false
|
28
|
+
end
|
29
|
+
|
30
|
+
def autodetect?
|
31
|
+
defined?(@autodetect) ? @autodetect : true
|
32
|
+
end
|
33
|
+
|
34
|
+
# Specify what feature the definition related to.
|
35
|
+
def for_feature(feature_label)
|
36
|
+
metadata[:for_feature] = feature_label
|
37
|
+
confine do
|
38
|
+
feature(feature_label)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module ClassMethods
|
44
|
+
def inherited(klass)
|
45
|
+
sub_classes << klass
|
46
|
+
end
|
47
|
+
|
48
|
+
# Override if the class should be used as parent class only.
|
49
|
+
# By default, we assume the class that does't inherit from class with
|
50
|
+
# Metadata is abstract = the base class of particular concept
|
51
|
+
def abstract_class
|
52
|
+
!(superclass < Metadata)
|
53
|
+
end
|
54
|
+
|
55
|
+
def sub_classes
|
56
|
+
@sub_classes ||= []
|
57
|
+
end
|
58
|
+
|
59
|
+
def all_sub_classes(ignore_abstract_classes = true)
|
60
|
+
ret = []
|
61
|
+
ret << self if !ignore_abstract_classes || !abstract_class
|
62
|
+
sub_classes.each do |sub_class|
|
63
|
+
ret.concat(sub_class.all_sub_classes(ignore_abstract_classes))
|
64
|
+
end
|
65
|
+
ret
|
66
|
+
end
|
67
|
+
|
68
|
+
def metadata
|
69
|
+
@metadata ||= initialize_metadata
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize_metadata
|
73
|
+
{ :tags => [],
|
74
|
+
:confine_blocks => [] }.tap do |metadata|
|
75
|
+
if superclass.respond_to?(:metadata)
|
76
|
+
metadata[:label] = superclass.metadata[:label]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.included(klass)
|
83
|
+
klass.extend(DSL)
|
84
|
+
klass.extend(ClassMethods)
|
85
|
+
end
|
86
|
+
|
87
|
+
def metadata
|
88
|
+
self.class.metadata
|
89
|
+
end
|
90
|
+
|
91
|
+
def label
|
92
|
+
metadata[:label] || generate_label
|
93
|
+
end
|
94
|
+
|
95
|
+
def description
|
96
|
+
metadata[:description] || to_s
|
97
|
+
end
|
98
|
+
|
99
|
+
def tags
|
100
|
+
metadata[:tags]
|
101
|
+
end
|
102
|
+
|
103
|
+
def present?
|
104
|
+
@_present ||= evaluate_confines
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def evaluate_confines
|
110
|
+
raise 'Recursive dependency in confine blocks detected' if @confines_evaluation_in_progress
|
111
|
+
@confines_evaluation_in_progress = true
|
112
|
+
metadata[:confine_blocks].all? do |block|
|
113
|
+
instance_exec(&block)
|
114
|
+
end
|
115
|
+
ensure
|
116
|
+
@confines_evaluation_in_progress = false
|
117
|
+
end
|
118
|
+
|
119
|
+
def generate_label
|
120
|
+
self.class.name.split('::').last.split(/(?=[A-Z])/).map(&:downcase).join('_').to_sym
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'csv'
|
3
|
+
require 'English'
|
4
|
+
|
5
|
+
module ForemanMaintain
|
6
|
+
module Concerns
|
7
|
+
module SystemHelpers
|
8
|
+
include Logger
|
9
|
+
|
10
|
+
def self.included(klass)
|
11
|
+
klass.extend(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
# class we use for comparing the versions
|
15
|
+
class Version < Gem::Version
|
16
|
+
end
|
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
|
25
|
+
|
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
|
32
|
+
end
|
33
|
+
|
34
|
+
def version(value)
|
35
|
+
Version.new(value)
|
36
|
+
end
|
37
|
+
|
38
|
+
def check_min_version(name, minimal_version)
|
39
|
+
current_version = rpm_version(name)
|
40
|
+
if current_version
|
41
|
+
return current_version >= version(minimal_version)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def downstream_installation?
|
46
|
+
execute?('rpm -q satellite') ||
|
47
|
+
(execute('rpm -q foreman') =~ /6sat.noarch/)
|
48
|
+
end
|
49
|
+
|
50
|
+
def rpm_version(name)
|
51
|
+
rpm_version = execute(%(rpm -q '#{name}' --queryformat="%{VERSION}"))
|
52
|
+
if $CHILD_STATUS.success?
|
53
|
+
version(rpm_version)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def parse_csv(data)
|
58
|
+
parsed_data = CSV.parse(data)
|
59
|
+
header = parsed_data.first
|
60
|
+
parsed_data[1..-1].map do |row|
|
61
|
+
Hash[*header.zip(row).flatten(1)]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def execute?(command, input = nil)
|
66
|
+
execute(command, input)
|
67
|
+
$CHILD_STATUS.success?
|
68
|
+
end
|
69
|
+
|
70
|
+
def execute!(command, input = nil)
|
71
|
+
output = execute(command, input)
|
72
|
+
if $CHILD_STATUS.success?
|
73
|
+
output
|
74
|
+
else
|
75
|
+
raise ExecutionError.new(command, input, output)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
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
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|