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.
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,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,13 @@
1
+ require 'logger'
2
+
3
+ module ForemanMaintain
4
+ module Concerns
5
+ module Logger
6
+ def logger
7
+ @logger ||= ::Logger.new($stderr).tap do |logger|
8
+ logger.level = ForemanMaintain.config.log_level
9
+ end
10
+ end
11
+ end
12
+ end
13
+ 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