src/lib/registration/ui/offline_migration_workflow.rb
# ------------------------------------------------------------------------------
# Copyright (c) 2018 SUSE LLC
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of version 2 of the GNU General Public License as published by the
# Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# ------------------------------------------------------------------------------
#
require "yast"
require "uri"
module Registration
module UI
# This class handles offline migration workflow,
# it is a wrapper around "migration_repos" client
class OfflineMigrationWorkflow
include Yast::I18n
include Yast::Logger
Yast.import "GetInstArgs"
Yast.import "Packages"
Yast.import "Installation"
Yast.import "Wizard"
Yast.import "Pkg"
Yast.import "AddOnProduct"
# the constructor
def initialize
textdomain "registration"
end
# The offline migration workflow is:
#
# - run the client which adds the new migration repositories
# - if it returns the :rollback status then run the registration rollback
# - return the user input symbol (:next, :back or :abort) to the caller
# @return [Symbol] the UI symbol
#
def main
log.info "Starting offline migration sequence"
# display an empty dialog just to hide the content of the previous step
Yast::Wizard.ClearContents
if Yast::GetInstArgs.going_back
going_back
return :back
end
reinit_repos
# run the main registration migration
ui = migration_repos
rollback if ui == :rollback
# refresh the add-on records
update_addon_records
if [:back, :abort, :rollback].include?(ui)
inst_sys_cleanup
# go back in the upgrade workflow after rollback or abort,
# maybe the user just selected a wrong partition to upgrade
ui = :back
end
log.info "Offline migration result: #{ui}"
ui
end
private
# force reloading of the old repositories so we can detect and remove the obsoleted
# services during migration (bsc#1159433)
def reinit_repos
Yast::Pkg.SourceSaveAll
Yast::Pkg.SourceFinishAll
Yast::Pkg.SourceRestore
end
def going_back
log.info("Going back")
if Registration.is_registered?
log.info("Restoring the previous registration")
rollback
end
inst_sys_cleanup
end
def rollback
Yast::WFM.CallFunction("registration_sync")
end
# cleanup the inst-sys, remove the files copied from the target system
def inst_sys_cleanup
# skip inst-sys cleanup if accidentally called in a running system
return unless Yast::Stage.initial
# remove the copied credentials file from the inst-sys
if File.exist?(SUSE::Connect::YaST::GLOBAL_CREDENTIALS_FILE)
log.info("Removing #{SUSE::Connect::YaST::GLOBAL_CREDENTIALS_FILE}...")
File.delete(SUSE::Connect::YaST::GLOBAL_CREDENTIALS_FILE)
end
certificate_cleanup if File.exist?(SslCertificate::INSTSYS_SERVER_CERT_FILE)
end
# remove the SSL certificate from the inst-sys
def certificate_cleanup
log.info("Removing the imported SSL certificate from the inst-sys...")
# Update database
Yast::Execute.locally("trust", "extract", "--format=openssl-directory",
"--filter=ca-anchors", "--overwrite", SslCertificate::TMP_CA_CERTS_DIR)
# Copy certificates/links
files = Dir[File.join(SslCertificate::TMP_CA_CERTS_DIR, "*")]
targets = ["pem", "openssl"].map { |d| File.join(SslCertificate::CA_CERTS_DIR, d) }
targets.each do |subdir|
files.each do |file|
path = File.join(subdir, File.basename(file))
if File.exist?(path)
log.info("Removing #{path}")
File.delete(path)
end
end
end
files = Dir[File.join(SslCertificate::INSTSYS_CERT_DIR, "*.pem")]
log.info("Removing files: #{files.inspect}")
File.delete(*files)
# Cleanup
FileUtils.rm_rf(SslCertificate::TMP_CA_CERTS_DIR)
end
def migration_repos
Yast::WFM.CallFunction("inst_migration_repos", [{ "enable_back" => true }])
end
# update the repository IDs in the AddOnProduct records, the migration
# updates the repository setup and the source IDs might not match anymore
def update_addon_records
Yast::AddOnProduct.add_on_products.each do |addon|
next unless addon["media_url"]
url = URI(addon["media_url"])
log.info("Refreshing repository ID for addon #{addon["product"]} (#{url})")
# remove the alias from the URL if it is preset, it is removed by Pkg bindings
# when adding the repository so it would not match
remove_alias(url)
update_addon(addon, url)
end
end
# remove the "alias" query URL parameter from the URL if it is present
# @param url [URI] input URL
def remove_alias(url)
if url.query
# params is a list of pairs, "foo=bar" => [["foo, "bar]]
params = URI.decode_www_form(url.query)
params.reject! { |p| p.first == "alias" }
# avoid empty query after "?" in URL
url.query = params.empty? ? nil : URI.encode_www_form(params)
end
end
# Find the repository ID for the URL and product dir
# @param url [URI] repository URL
# @param dir [String] product directory
# @return [Integer,nil] repository ID
def find_repo_id(url, dir)
Yast::Pkg.SourceGetCurrent(false).find do |repo|
data = Yast::Pkg.SourceGeneralData(repo)
# the same URL and product dir
URI(data["url"]) == url && data["product_dir"] == dir
end
end
# update an addon record
# @param addon [Hash] an addon record
# @param url [URI] URL of the addon (without "alias")
def update_addon(addon, url)
new_id = find_repo_id(url, addon["product_dir"])
if new_id
log.info("Updating ID: #{addon["media"]} -> #{new_id}")
addon["media"] = new_id
else
log.warn("Addon not found")
end
end
end
end
end