Project

General

Profile

Download (4.49 KB) Statistics
| Branch: | Tag: | Revision:
module ForemanMaintain
class UpgradeRunner < Runner
include Concerns::Finders

# Phases of the upgrade, see README.md for more info
PHASES = [:pre_upgrade_checks,
:pre_migrations,
:migrations,
:post_migrations,
:post_upgrade_checks].freeze

class << self
include Concerns::Finders

def available_targets
versions_to_tags.inject([]) do |available_targets, (version, tag)|
if !find_scenarios(:tags => [tag]).empty?
available_targets << version
else
available_targets
end
end
end

def versions_to_tags
@versions_to_tags ||= {}
end

# registers target version to specific tag
def register_version(version, tag)
if versions_to_tags.key?(version) && versions_to_tags[version] != tag
raise "Version #{version} already registered to tag #{versions_to_tags[version]}"
end
@versions_to_tags[version] = tag
end

def clear_register
versions_to_tags.lear
end
end

attr_reader :version, :tag, :phase

def initialize(version, reporter, options = {})
super(reporter, [], options)
@tag = self.class.versions_to_tags[version]
raise "Unknown version #{version}" unless tag
@version = version
@scenario_cache = {}
self.phase = :pre_upgrade_checks
end

def scenario(phase)
return @scenario_cache[phase] if @scenario_cache.key?(phase)
condition = { :tags => [tag, phase] }
matching_scenarios = find_scenarios(condition)
raise "Too many scenarios match #{condition.inspect}" if matching_scenarios.size > 1
@scenario_cache[phase] = matching_scenarios.first
end

def run
phases_to_run.each do |phase|
return run_rollback if quit?
run_phase(phase)
end
end

def run_rollback
# we only are able to rollback from pre_migrations phase
return if phase != :pre_migrations ||
!scenario(:pre_migrations).steps.any?(&:executed?)
pre_rollback_phase = phase
@quit = false
@last_scenario = nil # to prevent the unnecessary confirmation questions
[:post_migrations, :post_upgrade_checks].each do |phase|
if quit? && phase == :post_upgrade_checks
self.phase = pre_rollback_phase
return # rubocop:disable Lint/NonLocalExitFromIterator
end
run_phase(phase)
end
self.phase = :pre_upgrade_checks # rollback finished
end

def storage
ForemanMaintain.storage("upgrade_#{version}")
end

# serializes the state of the run to storage
def save
storage[:serialized] = to_hash
storage.save
end

# deserializes the state of the run from the storage
def load
load_from_hash(storage[:serialized])
end

def run_phase(phase)
next_scenario = scenario(phase)
return if next_scenario.nil? || next_scenario.steps.empty?
self.phase = phase
run_scenario(next_scenario)
# if we started from the :pre_upgrade_checks, ensure to ask before
# continuing with the rest of the upgrade
@ask_to_confirm_upgrade = phase == :pre_upgrade_checks
end

private

def to_hash
ret = { :phase => phase, :scenarios => {} }
@scenario_cache.each do |key, scenario|
ret[:scenarios][key] = scenario.to_hash
end
ret
end

def load_from_hash(hash)
unless @scenario_cache.empty?
raise "Some scenarios are already initialized: #{@scenario_cache.keys}"
end
self.phase = hash[:phase]
hash[:scenarios].each do |key, scenario_hash|
@scenario_cache[key] = Scenario.new_from_hash(scenario_hash)
end
end

def confirm_scenario(scenario)
decision = super(scenario)
# we have not asked the user already about next steps
if decision.nil? && @ask_to_confirm_upgrade
response = reporter.ask_decision(<<-MESSAGE.strip_heredoc.strip)
The script will now start with the modification part of the upgrade.
Confirm to continue
MESSAGE
ask_to_quit if [:no, :quit].include?(response)
end
response
ensure
@ask_to_confirm_upgrade = false
end

def phases_to_run
phases_to_run = PHASES.dup
phases_to_run.shift until phases_to_run.first == phase
phases_to_run
end

def phase=(phase)
raise "Unknown phase #{phase}" unless PHASES.include?(phase)
@phase = phase
end
end
end
(17-17/20)