diff --git a/package/yast2-installation.changes b/package/yast2-installation.changes index cd18c6d5d..2ed2a3ba8 100644 --- a/package/yast2-installation.changes +++ b/package/yast2-installation.changes @@ -1,3 +1,11 @@ +------------------------------------------------------------------- +Wed Jan 29 13:52:29 UTC 2020 - Ladislav Slezák + +- Reimplemented the "Previously Used Repositories" dialog + (inst_upgrade_urls.rb) to properly evaluate the new and the old + system repositories (bsc#1159433) +- 4.2.30 + ------------------------------------------------------------------- Thu Jan 23 12:53:29 UTC 2020 - Steffen Winterfeldt diff --git a/package/yast2-installation.spec b/package/yast2-installation.spec index b6674302d..879c883c9 100644 --- a/package/yast2-installation.spec +++ b/package/yast2-installation.spec @@ -16,7 +16,7 @@ # Name: yast2-installation -Version: 4.2.29 +Version: 4.2.30 Release: 0 Group: System/YaST License: GPL-2.0-only diff --git a/src/lib/installation/clients/inst_upgrade_urls.rb b/src/lib/installation/clients/inst_upgrade_urls.rb index efbc1bc3c..2cc31225f 100644 --- a/src/lib/installation/clients/inst_upgrade_urls.rb +++ b/src/lib/installation/clients/inst_upgrade_urls.rb @@ -2,7 +2,7 @@ # ------------------------------------------------------------------------------ # Copyright (c) 2006-2012 Novell, Inc. All Rights Reserved. -# +# Copyright (c) 2020 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 @@ -19,208 +19,90 @@ # current contact information at www.novell.com. # ------------------------------------------------------------------------------ -require "fileutils" +require "yast" +require "installation/upgrade_repo_manager" +require "y2packager/repository" + +Yast.import "GetInstArgs" +Yast.import "Label" +Yast.import "Mode" +Yast.import "Pkg" +Yast.import "Popup" +Yast.import "Stage" +Yast.import "UI" +Yast.import "Wizard" module Yast + # This client allows reusing the old repositories during system upgrade. + # + # @note For testing in an installed system use this command: + # YAST_TEST=1 yast2 inst_upgrade_urls + # It will load the current repositories from the system, + # the changes will NOT be saved. class InstUpgradeUrlsClient < Client include Yast::Logger - URL_KEYS = ["alias", "autorefresh", "url", "name", "keeppackages", - "type", "id", "initial_url_status", "new_status"].freeze - def main - Yast.import "Pkg" - Yast.import "UI" - # FATE #301785: Distribution upgrade should offer - # existing extra installation repository as Add-On - - Yast.import "Installation" - Yast.import "FileUtils" - Yast.import "Stage" - Yast.import "GetInstArgs" - Yast.import "Mode" - Yast.import "Wizard" - Yast.import "Progress" - Yast.import "Label" - Yast.import "NetworkService" - Yast.import "Popup" - Yast.import "AddOnProduct" - Yast.import "Report" - Yast.import "PackageCallbacks" - textdomain "installation" - @ret = :next - @ret = :back if GetInstArgs.going_back - - @test_mode = false - - @do_not_remove = 0 + ret = GetInstArgs.going_back ? :back : :next - if Ops.greater_than(Builtins.size(WFM.Args), 0) && - Ops.is_string?(WFM.Args(0)) - Builtins.y2milestone("Args: %1", WFM.Args) - @test_mode = true if WFM.Args(0) == "test" - end - - if @test_mode - Builtins.y2milestone("Test mode") - else - if !Stage.initial - Builtins.y2milestone("Not an initial stage") - return @ret - end - if !Mode.update - Builtins.y2milestone("Not an update mode") - return @ret - end + if test? + log.info("Test mode activated") + init_pkg_mgr + elsif !Stage.initial || !Mode.update + log.info("Not in update mode or initial stage") + return ret end - @dir_new = Builtins.sformat("%1/etc/zypp/repos.d/", Installation.destdir) - - @system_urls = [] - - @already_registered_repos = [] - - @new_repos_already_in_system = false - - @REPO_REMOVED = "removed" - @REPO_ENABLED = "enabled" - @REPO_DISABLED = "disabled" + # use ::Installation avoid collision with Yast::Installation + @repo_manager = ::Installation::UpgradeRepoManager.create_from_old_repositories - @repos_to_remove = [] - - # repositories for removal (the repo files will be removed directly) - @repo_files_to_remove = [] - - @repos_to_add = [] - - @id_to_url = {} + # nothing to show + if repo_manager.repositories.empty? + log.info("No old repository defined, skipping the dialog") + return ret + end - # bnc #308763 - @repos_to_add_disabled = [] + ret = run_dialog - @urls = [] + log.info("Returning #{ret.inspect}") + ret + end - @id_to_name = {} + private - @status_map = { - # TRANSLATORS: Table item status (repository) - @REPO_REMOVED => _( - "Removed" - ), - # TRANSLATORS: Table item status (repository) - @REPO_ENABLED => _( - "Enabled" - ), - # TRANSLATORS: Table item status (repository) - @REPO_DISABLED => _( - "Disabled" - ) - } + attr_reader :repo_manager - # for testing purpose + # Run the main dialog + # + # @return [Symbol] the UserInput symbol + def run_dialog + # just for testing purposes Wizard.CreateDialog if Mode.normal - Progress.NextStage - - ReadZyppRepositories() - - Progress.NextStage - - @continue_processing = false - - if !@system_urls.nil? && @system_urls != [] - @continue_processing = true - # initialize zypp - Pkg.TargetInitialize(Installation.destdir) - # bnc #429080 - Pkg.TargetLoad - # Note: does not work when a repository is already registered - # in pkg-bindings! - Pkg.SourceStartManager(false) - - @current_repos_list = Pkg.SourceGetCurrent( - false # not only enabled - ) - Builtins.y2milestone( - "Currently registered repos: %1", - @current_repos_list - ) - - Builtins.foreach(@current_repos_list) do |one_id| - source_data = Pkg.SourceGeneralData(one_id) - Ops.set(source_data, "media", one_id) - @already_registered_repos = Builtins.add( - @already_registered_repos, - source_data - ) - end - end - - # bnc #400823 - @do_not_remove = Ops.get(Pkg.SourceGetCurrent(false), 0, 0) - - FillUpURLs() - RemoveInstallationReposFromUpgrededSystemOnes() + display_dialog + refresh_dialog - Progress.NextStage - Progress.Finish - - # Displaying a screen which includes repos that where not originally - # in the system being upgraded can be quite confusing. Let's restore - # the partition backup and start again - if @new_repos_already_in_system && @ret == :back - log.info "Target system already poluted with new repositories" - @continue_processing = false - elsif @already_registered_repos.nil? || - Ops.less_than(Builtins.size(@already_registered_repos), 1) - Builtins.y2milestone("No repositories found") - @continue_processing = false - elsif @urls.nil? || Ops.less_than(Builtins.size(@urls), 1) - Builtins.y2milestone("No repositories to offer") - @continue_processing = false - end - - if @continue_processing - @ret = HandleOldSources() - @ret = AddOrRemoveRepositories() if @ret == :next + ret = handle_dialog + if ret == :next + repo_manager.activate_changes + save_pkg_mgr end - # for testing purpose - if Mode.normal - Pkg.SourceSaveAll if @ret == :next - Wizard.CloseDialog - end - - Builtins.y2milestone("Returning %1", @ret) - @ret - end + # just for testing purposes + Wizard.CloseDialog if Mode.normal - def ReadZyppRepositories - # New-type URLs - @system_urls = Convert.convert( - SCR.Read(path(".zypp_repos"), @dir_new), - from: "any", - to: "list " - ) - - if @system_urls.nil? || @system_urls == [] - Builtins.y2milestone("No zypp repositories on the target") - else - Builtins.y2milestone("URLs: %1", @system_urls) - end - - nil + ret end - def CreateListTableUI + def display_dialog Wizard.SetContents( # TRANSLATORS: dialog caption _("Previously Used Repositories"), VBox( # TRANSLATORS: dialog text, possibly multiline, - # Please, do not use more than 50 characters per line. + # Please, do not use more than about 50 characters per line. Left( Label( _( @@ -269,106 +151,53 @@ def CreateListTableUI nil end - def RedrawListTableUI - currentitem = Convert.to_integer( - UI.QueryWidget(Id("table_of_repos"), :CurrentItem) - ) + def translated_repo_status(status) + case status + when :removed + # TRANSLATORS: The repository status + _("Removed") + when :disabled + # TRANSLATORS: The repository status + _("Disabled") + when :enabled + # TRANSLATORS: The repository status + _("Enabled") + else + # TRANSLATORS: The repository status is unknown + _("Unknown") + end + end - counter = -1 - items = Builtins.maplist(@urls) do |one_url| - counter = Ops.add(counter, 1) - # one_url already has "id" and some items might be deleted - # looking to id_to_name is done via the original key - Ops.set( - @id_to_name, - Ops.get_string(one_url, "id", Builtins.sformat("ID: %1", counter)), - Ops.get_locale(one_url, "name", _("Unknown")) - ) + def refresh_dialog + current_item = UI.QueryWidget(Id("table_of_repos"), :CurrentItem) + + items = repo_manager.repositories.map do |r| Item( - Id(counter), - Ops.get( - @status_map, - Ops.get_string(one_url, "new_status", @REPO_REMOVED), - _("Unknown") - ), - Ops.get_locale( - # TRANSLATORS: Fallback name for a repository - one_url, - "name", - _("Unknown") - ), - Ops.get_string(one_url, "url", "") + Id(r.repo_alias), + translated_repo_status(repo_manager.repo_status(r)), + r.name, + repo_manager.repo_url(r) ) end - # bnc #390612 - items = Builtins.sort(items) do |a, b| - Ops.less_than(Ops.get_string(a, 2, ""), Ops.get_string(b, 2, "")) - end - UI.ChangeWidget(Id("table_of_repos"), :Items, items) - if !currentitem.nil? - UI.ChangeWidget(Id("table_of_repos"), :CurrentItem, currentitem) - end - - enable_buttons = Ops.greater_than(Builtins.size(items), 0) - UI.ChangeWidget(Id(:edit), :Enabled, enable_buttons) - UI.ChangeWidget(Id(:toggle), :Enabled, enable_buttons) - - nil + return unless current_item + UI.ChangeWidget(Id("table_of_repos"), :CurrentItem, current_item) end - def FindURLName(id) - if id == "" || id.nil? - Builtins.y2error("Base URL not defined!") - return nil - end - - ret = nil - - Builtins.foreach(@urls) do |one_url| - if id == Ops.get_string(one_url, "id", "-A-") && - Ops.get_string(one_url, "name", "") != "" - ret = Ops.get_string(one_url, "name", "") - raise Break - end - end - - ret + def find_repo(repo_alias) + repo_manager.repositories.find { |r| r.repo_alias == repo_alias } end - def FindURLType(id) - if id == "" || id.nil? - Builtins.y2error("Base URL not defined!") - return "" - end - - ret = "" - - Builtins.foreach(@urls) do |one_url| - if id == Ops.get_string(one_url, "id", "-A-") && - Ops.get_string(one_url, "type", "") != "" - ret = Ops.get_string(one_url, "type", "") - raise Break - end - end - - ret - end - - def EditItem(currentitem) - if currentitem.nil? || Ops.less_than(currentitem, 0) - Builtins.y2error("Cannot edit item: %1", currentitem) - return - end - - url = Ops.get_string(@urls, [currentitem, "url"], "") - min_width = Builtins.size(url) + def edit_item(repo) + url = repo_manager.repo_url(repo) + # limit the minimal width + min_width = [url.size, 60].max UI.OpenDialog( VBox( - # TRANSLATORS: textentry + # TRANSLATORS: Text entry for changing the repositoru URL MinWidth(min_width, TextEntry(Id(:url), _("&Repository URL"), url)), VSpacing(1), ButtonBox( @@ -387,979 +216,85 @@ def EditItem(currentitem) ) ret = UI.UserInput - url = Convert.to_string(UI.QueryWidget(Id(:url), :Value)) + url = UI.QueryWidget(Id(:url), :Value) UI.CloseDialog - return if ret == :cancel - - Ops.set(@urls, [currentitem, "url"], url) + return unless ret == :ok - nil + repo_manager.change_url(repo, url) end - def FillUpURLs - @urls = [] - - # If some new (since 10.3 Alpha?) URLs found, use only them - if !@system_urls.nil? && @system_urls != [] - Builtins.foreach(@system_urls) do |one_url_map| - # If the user has gone back and forward, some of the repositories of - # the previous system could be already registered for this - # installation - repo = equivalent_repo_for(one_url_map) - if repo - # The installation repository is already in @system_urls. Most - # likely there will be also another repositories added by the - # installation process (not from the previous system) - if repo["media"] == @do_not_remove - @new_repos_already_in_system = true - else - # For any repository not being the installation one, send it - # back to the list of found urls - new_url_map = repo_to_url(repo) - unregister(repo) - new_url_map["initial_url_status"] = @REPO_REMOVED - log.info "Repository moved to offered urls: #{new_url_map["name"]}" - @urls << new_url_map - end - else - @urls << zypp_repo_to_url(one_url_map) - end - end - end + def handle_dialog + log.info("Offering repositories: #{repo_manager.repositories.map(&:repo_alias).inspect}") - id = -1 - url_alias = "" - - @urls = Builtins.maplist(@urls) do |one_url| - id = Ops.add(id, 1) - # unique ID - Ops.set(one_url, "id", Builtins.sformat("ID: %1", id)) - # BNC #429059 - if one_url["alias"] - url_alias = Builtins.sformat( - "%1", - Ops.get_string(one_url, "alias", "") - ) - else - Builtins.y2warning("No 'alias' defined: %1", one_url) - end - deep_copy(one_url) - end - - nil - end - - # Function removes repositories already registered by the installation - # from list of urls found on the system. - def RemoveInstallationReposFromUpgrededSystemOnes - # Works only for the very first registered (installation) repo - found = false - - # All already registered repos - Builtins.foreach(@already_registered_repos) do |one_registered_repo| - if Ops.get_boolean(one_registered_repo, @REPO_ENABLED, true) == false - next - end - raise Break if found == true - found = true - # if an installation repository is disabled, skip it - if Ops.get_boolean(one_registered_repo, @REPO_ENABLED, true) == false - Builtins.y2milestone( - "Repo %1 is not enabled, skipping", - Ops.get( - one_registered_repo, - "url", - Ops.get(one_registered_repo, "media") - ) - ) - next - end - registered_url = Ops.get_string(one_registered_repo, "url", "-A-") - registered_name = Ops.get_string(one_registered_repo, "name", "-A-") - registered_dir = Ops.get_string(one_registered_repo, "path", "/") - # Remove them from repos being offered to user to enable/disable them - # Don't handle them at all, they have to stay untouched - # See bnc #360109 - @urls = Builtins.filter(@urls) do |one_from_urls| - one_url = Ops.get_string(one_from_urls, "url", "-B-") - one_name = Ops.get_string(one_from_urls, "name", "-B-") - one_dir = Ops.get_string(one_from_urls, "path", "/") - if registered_url == one_url && registered_name == one_name && - registered_dir == one_dir - Builtins.y2milestone( - "The same product (url) already in use, not handling it %1", - one_registered_repo - ) - next false - else - next true - end - end - end - - nil - end - - # BNC #583155: Removed/Enabled/Disabled - # Toggled this way: R/E/D/R/E/D/... - def ToggleStatus(repo_map) - repo_map = deep_copy(repo_map) - status = Ops.get_string(repo_map, "new_status", @REPO_REMOVED) - - status = if status == @REPO_REMOVED - @REPO_ENABLED - elsif status == @REPO_ENABLED - @REPO_DISABLED - # disabled - else - @REPO_REMOVED - end - - status - end - - def HandleOldSources - Builtins.y2milestone("Offering: %1", @urls) - Builtins.y2milestone("Already registered: %1", @already_registered_repos) - - CreateListTableUI() - RedrawListTableUI() - - ret = :next - ui_ret = nil - - loop do - ui_ret = UI.UserInput - - ui_ret = :toggle if ui_ret == "table_of_repos" - - if ui_ret == :toggle - currentitem = Convert.to_integer( - UI.QueryWidget(Id("table_of_repos"), :CurrentItem) - ) - if !currentitem.nil? - # BNC #583155: Removed/Enabled/Disabled - Ops.set( - @urls, - [currentitem, "new_status"], - ToggleStatus(Ops.get(@urls, currentitem, {})) - ) - RedrawListTableUI() - end - next - elsif ui_ret == :next - ret = :next - break - elsif ui_ret == :back - ret = :back - break - elsif ui_ret == :abort && Popup.ConfirmAbort(:painless) - ret = :abort - break - elsif ui_ret == :edit - currentitem = Convert.to_integer( - UI.QueryWidget(Id("table_of_repos"), :CurrentItem) - ) - EditItem(currentitem) - RedrawListTableUI() - else - Builtins.y2error("Unknown UI input: %1", ui_ret) - end - end - - ret - end - - def NetworkRunning - ret = false + ret = nil loop do - if NetworkService.isNetworkRunning - ret = true - break - end - - # Network is not running - if !Popup.AnyQuestion( - # TRANSLATORS: popup header - _("Network is not Configured"), - # TRANSLATORS: popup question - _( - "Remote repositories require an Internet connection.\nConfigure it?" - ), - Label.YesButton, - Label.NoButton, - :yes - ) - Builtins.y2milestone("User decided not to setup the network") - ret = false - break - end - - Builtins.y2milestone("User wants to setup the network") - # Call network-setup client - netret = WFM.call("inst_lan", [GetInstArgs.argmap.merge("skip_detection" => true)]) - - if netret == :abort - Builtins.y2milestone("Aborting the network setup") - break - end - end - - ret - end - - def SetAddRemoveSourcesUI - Wizard.SetContents( - # TRANSLATORS: dialog caption - _("Previously Used Repositories"), - VBox( - # TRANSLATORS: Progress text - Label(_("Adding and removing repositories...")) - ), - # TRANSLATORS: help text - _("

Repositories are being added and removed.

"), - false, - false - ) - - nil - end - - def SetAddRemoveSourcesProgress - actions_todo = [] - actions_doing = [] - - steps_nr = 0 - - if Ops.greater_than(Builtins.size(@repos_to_remove), 0) - Builtins.y2milestone("Remove %1 repos", @repos_to_remove) - actions_todo = Builtins.add( - actions_todo, - _("Remove unused repositories") - ) - actions_doing = Builtins.add( - actions_doing, - _("Removing unused repositories...") - ) - steps_nr = Ops.add(steps_nr, Builtins.size(@repos_to_remove)) - end - - if Ops.greater_than(Builtins.size(@repos_to_add), 0) - Builtins.y2milestone("Add %1 enabled repos", @repos_to_add) - actions_todo = Builtins.add(actions_todo, _("Add enabled repositories")) - actions_doing = Builtins.add( - actions_doing, - _("Adding enabled repositories...") - ) - steps_nr = Ops.add(steps_nr, Builtins.size(@repos_to_add)) - end - - if Ops.greater_than(Builtins.size(@repos_to_add_disabled), 0) - Builtins.y2milestone("Add %1 disabled repos", @repos_to_add_disabled) - actions_todo = Builtins.add( - actions_todo, - _("Add disabled repositories") - ) - actions_doing = Builtins.add( - actions_doing, - _("Adding disabled repositories...") - ) - steps_nr = Ops.add(steps_nr, Builtins.size(@repos_to_add_disabled)) - end - - Progress.New( - # TRANSLATORS: dialog caption - _("Previously Used Repositories"), - _("Adding and removing repositories..."), - steps_nr, - actions_todo, - actions_doing, - # TRANSLATORS: help text - _("

Repositories are being added and removed.

") - ) - - nil - end - - # See bnc #309317 - def GetUniqueAlias(alias_orig) - alias_orig = "" if alias_orig.nil? - - # all current aliases - aliases = Builtins.maplist(Pkg.SourceGetCurrent(false)) do |i| - info = Pkg.SourceGeneralData(i) - Ops.get_string(info, "alias", "") - end - - # default - alias_ = alias_orig - - # repository alias must be unique - # if it already exists add "_" suffix to it - idx = 1 - while Builtins.contains(aliases, alias_) - alias_ = Builtins.sformat("%1_%2", alias_orig, idx) - idx = Ops.add(idx, 1) - end - - if alias_orig != alias_ - Builtins.y2milestone("Alias '%1' changed to '%2'", alias_orig, alias_) - end - - alias_ - end - - def AdjustRepoSettings(new_repo, id) - if id.nil? || id == "" - Builtins.y2error("Undefined ID: %1", id) - return - end - - Builtins.foreach(@urls) do |one_url| - if Ops.get_string(one_url, "id", "") == id - Builtins.y2milestone("Matching: %1", one_url) - - # alias needs to be unique - # bnc #309317 - # - # alias is taken from the system first - # bnc #387261 - # - Ops.set( - new_repo.value, - "alias", - GetUniqueAlias(Ops.get_string(one_url, "alias", "")) - ) - - Builtins.foreach(["autorefresh", "gpgcheck", "keeppackages"]) do |key| - if Builtins.haskey(one_url, key) - Ops.set(new_repo.value, key, Ops.get_boolean(one_url, key, true)) - end - end - - if Builtins.haskey(one_url, "priority") - Ops.set( - new_repo.value, - "priority", - Ops.get_integer(one_url, "priority", 99) - ) - end - - raise Break - end - end - - nil - end - - # Removes selected repositories - def IUU_RemoveRepositories - if !@repo_files_to_remove.empty? - backup_dir = File.join(Installation.destdir, "var/adm/backup/upgrade/zypp/repos.d") + ret = UI.UserInput + ret = :abort if ret == :cancel - ::FileUtils.mkdir_p(backup_dir) + case ret + when :toggle, :edit, "table_of_repos" + current_item = UI.QueryWidget(Id("table_of_repos"), :CurrentItem) + next unless current_item - @repo_files_to_remove.each do |repo| - log.info "Removing repository: #{repo}" - if !repo["alias"] - log.warn "Repo alias is nil -> not removing it" + selected_repo = repo_manager.repositories.find { |r| r.repo_alias == current_item } + if !selected_repo + log.error("Selected repository #{current_item.inspect} not found???") next end - repo_alias_regexp = /^\s*\[#{Regexp.escape(repo["alias"])}\]\s*$/ - path = File.join(Installation.destdir, "etc/zypp/repos.d", "#{repo["alias"]}.repo") - # quick search: check if the .repo file exists with the repository - if File.exist?(path) && File.read(path).match(repo_alias_regexp) - log.info "Removing file #{path} (backed up in #{backup_dir})" - ::FileUtils.mv(path, backup_dir) + if ret == :edit + edit_item(selected_repo) else - # do a full search: find the appropriate repo file - repo_file = Dir[File.join(Installation.destdir, "etc/zypp/repos.d/*.repo")].find do |file| - File.read(file).match(repo_alias_regexp) - end - - if repo_file - log.info "Removing file #{repo_file} (backed up in #{backup_dir})" - ::FileUtils.mv(repo_file, backup_dir) - else - log.warn "Repofile for repository #{repo["alias"]} was not found, not removing" - end + repo_manager.toggle_repo_status(selected_repo) end - end - - # force reloading the libzypp repomanager to notice the removed files - Pkg.TargetFinish - Pkg.TargetInitializeOptions(Installation.destdir, - "target_distro" => target_distribution) - Pkg.TargetLoad - end - - return if Builtins.size(@repos_to_remove) == 0 - - Progress.Title(_("Removing unused repositories...")) - Progress.NextStage - - Builtins.y2milestone("Deleting repos: %1", @repos_to_remove) - Builtins.foreach(@repos_to_remove) do |one_id| - Progress.NextStep - Pkg.SourceDelete(one_id) - AddOnProduct.add_on_products = Convert.convert( - Builtins.filter(AddOnProduct.add_on_products) do |one_addon| - Ops.get_integer(one_addon, "media", -42) != one_id - end, - from: "list ", - to: "list >" - ) - end - - nil - end - - def remove_services - service_files = Dir[File.join(Installation.destdir, "/etc/zypp/services.d/*.service")] - - return if service_files.empty? - - backup_dir = File.join(Installation.destdir, "var/adm/backup/upgrade/zypp/services.d") - ::FileUtils.mkdir_p(backup_dir) - - log.info "Moving #{service_files} to #{backup_dir}" - ::FileUtils.mv(service_files, backup_dir) - end - - def InsertCorrectMediaHandler(url, name) - if !Builtins.regexpmatch(url, "^cd:/") && - !Builtins.regexpmatch(url, "^dvd:/") - Builtins.y2milestone("URL is not a CD/DVD") - return true - end - - # true - OK, continue - if Popup.AnyQuestion( - _("Correct Media Requested"), - Builtins.sformat( - _( - "Make sure that media with label %1\n" \ - "is in the CD/DVD drive.\n" \ - "\n" \ - "If you skip it, the repository will not be added.\n" - ), - name - ), - Label.OKButton, - Label.SkipButton, - :yes - ) == true - Pkg.SourceReleaseAll - return true - end - - # false - skip - false - end - - # Adds selected repositories as enabled - def IUU_AddEnabledRepositories - return if Builtins.size(@repos_to_add) == 0 - - Progress.Title(_("Adding enabled repositories...")) - Progress.NextStage - - # Adding repositories in a disabled state, then enable them - # for the system upgrade - Builtins.foreach(@repos_to_add) do |one_id| - Builtins.y2milestone("Adding repository: %1", one_id) - Progress.NextStep - one_url = Ops.get(@id_to_url, one_id, "") - repo_name = Ops.get(@id_to_name, one_id, "") - pth = "/" - if one_url.nil? || one_url == "" - Builtins.y2error("Repository id %1 has no URL", one_id) - next - end - if InsertCorrectMediaHandler(one_url, repo_name) != true - Builtins.y2warning("Skipping repository %1", one_id) - next - end - repo_type = Pkg.RepositoryProbe(one_url, "/") - Builtins.y2milestone( - "Probed repository: %1 type: %2", - one_url, - repo_type - ) - if (repo_type.nil? || repo_type == "NONE") && - Builtins.substring(one_url, 0, 4) == "dir:" - one_url = Ops.add("dir:/mnt", Builtins.substring(one_url, 4)) - repo_type = Pkg.RepositoryProbe(one_url, "/") - Builtins.y2milestone( - "Probed possible local repository again: %1 type: %2", - one_url, - repo_type - ) - end - if repo_type.nil? || repo_type == "NONE" - Builtins.y2error("Cannot probe repository %1", one_id) - Report.Error( - Builtins.sformat( - _( - "Cannot add repository %1\n" \ - "URL: %2\n" \ - "\n" \ - "\n" \ - "Repository will be added in disabled state." - ), - repo_name, - one_url - ) - ) - - # see bnc#779396 - # Repository cannot be probed, it has to be added in disabled state - @repos_to_add_disabled = Builtins.add(@repos_to_add_disabled, one_id) - next - end - # see bnc #310209 - # Adding repositories with their correct names - repoadd = { - @REPO_ENABLED => false, - "name" => repo_name, - "base_urls" => [one_url], - "prod_dir" => pth, - "type" => repo_type, - # bnc #543468, do not check aliases of repositories stored in Installation::destdir - "check_alias" => false - } - repoadd_ref = arg_ref(repoadd) - AdjustRepoSettings(repoadd_ref, one_id) - repoadd = repoadd_ref.value - Builtins.y2milestone("Adding repo (enabled): %1", repoadd) - new_id = Pkg.RepositoryAdd(repoadd) - if new_id.nil? || new_id == -1 - Builtins.y2error("Error adding repository: %1", repoadd) - Report.Error( - Builtins.sformat( - _( - "Cannot add enabled repository\n" \ - "Name: %1\n" \ - "URL: %2" - ), - repo_name, - one_url - ) - ) - next - end - if Ops.greater_than(new_id, -1) - repo_refresh = Pkg.SourceRefreshNow(new_id) - Builtins.y2milestone("Repository refreshed: %1", repo_refresh) - - if repo_refresh != true - Report.Error( - Builtins.sformat( - # TRANSLATORS: error report - # %1 is replaced with repo-name, %2 with repo-URL - _( - "An error occurred while refreshing repository\n" \ - "Name: %1\n" \ - "URL: %2" - ), - repo_name, - one_url - ) - ) - next - end - - repo_enable = Pkg.SourceSetEnabled(new_id, true) - Builtins.y2milestone("Repository enabled: %1", repo_enable) - - if repo_enable != true - Report.Error( - Builtins.sformat( - # TRANSLATORS: error report - # %1 is replaced with repo-name, %2 with repo-URL - _( - "An error occurred while enabling repository\n" \ - "Name: %1\n" \ - "URL: %2\n" - ), - repo_name, - one_url - ) - ) - next - end - - AddOnProduct.Integrate(new_id) - - prod = Convert.convert( - Pkg.SourceProductData(new_id), - from: "map ", - to: "map " - ) - Builtins.y2milestone("Product Data: %1", prod) - - AddOnProduct.add_on_products = Builtins.add( - AddOnProduct.add_on_products, - - "media" => new_id, - "media_url" => one_url, - "product_dir" => pth, - "product" => repo_name, - "autoyast_product" => repo_name - - ) - end - end - - nil - end - # Adds selected repositories as disabled - def IUU_AddDisabledRepositories - return if Builtins.size(@repos_to_add_disabled) == 0 - - Progress.Title(_("Adding disabled repositories...")) - Progress.NextStage - - # Adding the rest of repositories in a disabled state - # bnc #326342 - Builtins.y2milestone("Adding DISABLED repos: %1", @repos_to_add_disabled) - - Builtins.foreach(@repos_to_add_disabled) do |one_id| - Progress.NextStep - one_url = Ops.get(@id_to_url, one_id, "") - repo_name = Ops.get(@id_to_name, one_id, "") - pth = "/" - if InsertCorrectMediaHandler(one_url, repo_name) != true - Builtins.y2warning("Skipping repository %1", one_id) - next - end - # see bnc #310209 - # Adding repositories with their correct names - repoadd = { - @REPO_ENABLED => false, - "name" => repo_name, - "base_urls" => [one_url], - "prod_dir" => pth, - # bnc #543468, do not check aliases of repositories stored in Installation::destdir - "check_alias" => false - } - repoadd_ref = arg_ref(repoadd) - AdjustRepoSettings(repoadd_ref, one_id) - repoadd = repoadd_ref.value - # do not probe! adding as disabled! - repo_type = FindURLType(one_url) - if !repo_type.nil? && repo_type != "" - Ops.set(repoadd, "type", repo_type) - end - Builtins.y2milestone("Adding repo (disabled): %1", repoadd) - new_id = Pkg.RepositoryAdd(repoadd) - if new_id.nil? || new_id == -1 - Builtins.y2error("Error adding repository: %1", repoadd) - Report.Error( - Builtins.sformat( - _( - "Cannot add disabled repository\n" \ - "Name: %1\n" \ - "URL: %2" - ), - repo_name, - one_url - ) - ) - end - end - - nil - end - - def SourceIsRemote(url) - return false if Builtins.regexpmatch(url, "^cd://") - return false if Builtins.regexpmatch(url, "^dvd://") - return false if Builtins.regexpmatch(url, "^disk://") - - true - end - - def FindMediaNr(alias_, url) - if alias_ == "" || alias_.nil? - Builtins.y2error("alias not defined!") - return nil - end - - if url == "" || url.nil? - Builtins.y2error("URL not defined!") - return nil - end - - ret = nil - - Builtins.foreach(@already_registered_repos) do |one_url| - if alias_ == Ops.get_string(one_url, "alias", "-A-") && - url == Ops.get_string(one_url, "url", "-A-") - ret = Ops.get_integer(one_url, "media", -1) - raise Break + refresh_dialog + when :next, :back + break + when :abort + break if Popup.ConfirmAbort(:painless) + else + log.warn("Unknown input: #{ret}") end end ret end - def AddOrRemoveRepositories - @repos_to_remove = [] - @repos_to_add = [] - @id_to_url = {} - @repos_to_add_disabled = [] - @repo_files_to_remove = [] - - # bnc #400823 - @do_not_remove = Ops.get(Pkg.SourceGetCurrent(false), 0, 0) - - some_sources_are_remote = false - - Builtins.foreach(@urls) do |one_source| - url = Ops.get_string(one_source, "url", "") - id = Ops.get_string(one_source, "id", "") - some_sources_are_remote = true if SourceIsRemote(url) - Ops.set(@id_to_url, id, url) - # bnc #400823 - current_medianr = FindMediaNr( - Builtins.tostring(Ops.get(one_source, "alias")), - Builtins.tostring(Ops.get(one_source, "url")) - ) - if @do_not_remove == current_medianr - Builtins.y2milestone( - "Skipping installation repository: %1", - @do_not_remove - ) - next - end - Builtins.y2milestone("Checking repo: %1", one_source) - # Source should be enabled at the end - if Ops.get_string(one_source, "new_status", "") == @REPO_ENABLED - if Ops.get_string(one_source, "initial_url_status", "") == @REPO_ENABLED - Builtins.y2milestone("Repository has been already enabled") - else - # It's not yet enabled, add it - @repos_to_add = Builtins.add(@repos_to_add, id) - Builtins.y2milestone("Repository to add: %1", id) - - # It's been already added but in disabled state - if Ops.get_string(one_source, "initial_url_status", "") == @REPO_DISABLED - @repos_to_remove = Builtins.add(@repos_to_remove, current_medianr) - Builtins.y2milestone("Repository to remove: %1", current_medianr) - end - end - - # Repository should be removed (not added) - elsif Ops.get_string(one_source, "new_status", "") == @REPO_REMOVED - if Ops.get_string(one_source, "initial_url_status", "") == @REPO_REMOVED - log.info "Repository not loaded or already removed" - # repository is not known to pkg-bindings, remove the repo file directly - @repo_files_to_remove << one_source - else - @repos_to_remove = Builtins.add(@repos_to_remove, current_medianr) - Builtins.y2milestone("Repository to remove: %1", current_medianr) - end - - # Repositry will be added in disabled state - # BNC #583155 - elsif Ops.get_string(one_source, "new_status", "") == @REPO_DISABLED - # It's been already added in enabled state - if Ops.get_string(one_source, "initial_url_status", "") == @REPO_ENABLED - @repos_to_remove = Builtins.add(@repos_to_remove, current_medianr) - Builtins.y2milestone("Repository to remove: %1", current_medianr) - end - - @repos_to_add_disabled = Builtins.add(@repos_to_add_disabled, id) - Builtins.y2milestone("Repository to add disabled: %1", id) - end - end - - if Ops.greater_than(Builtins.size(@repos_to_remove), 0) || - Ops.greater_than(Builtins.size(@repos_to_add), 0) - SetAddRemoveSourcesUI() - end - - # BNC #478024: Remote repositories need a running network - if Ops.greater_than(Builtins.size(@repos_to_add), 0) && !NetworkRunning() - Builtins.y2milestone( - "No network is running, trying inst_network_check fallback" - ) - ret = WFM.CallFunction("inst_network_check", []) - Builtins.y2milestone("Called inst_network_check returned: %1", ret) - end - - # Remote repositories without running network are registered - # as disabled - if Ops.greater_than(Builtins.size(@repos_to_add), 0) && !NetworkRunning() - Builtins.y2warning( - "Network is not running, repositories will be added in DISABLED state" - ) - @repos_to_add_disabled = Convert.convert( - Builtins.union(@repos_to_add_disabled, @repos_to_add), - from: "list", - to: "list " - ) - @repos_to_add = [] - end - - @repos_to_remove = Builtins.filter(@repos_to_remove) do |one_source| - one_source != @do_not_remove - end - - progress = Progress.status - - Progress.set(false) if Builtins.size(@repos_to_add) == 0 - - SetAddRemoveSourcesProgress() - - PackageCallbacks.RegisterEmptyProgressCallbacks - - # (re)move old services - there is no UI for services, - # but we really need to get rid of the old NCC service... - remove_services - - IUU_RemoveRepositories() - - # Add repositories in enabled state - IUU_AddEnabledRepositories() - - # Add repositories in disabled state - IUU_AddDisabledRepositories() - - Progress.Finish - - PackageCallbacks.RestorePreviousProgressCallbacks - - Progress.set(progress) if Builtins.size(@repos_to_add) == 0 - - :next - end - - private - - # FIXME: share this code better - def target_distribution - base_products = Product.FindBaseProducts - - # empty target distribution disables service compatibility check in case - # the base product cannot be found - target_distro = base_products ? base_products.first["register_target"] : "" - log.info "Base product target distribution: #{target_distro}" - - target_distro - end - - # Returns an url description based on a registered repo information - # - # @param repo [Hash] an entry from @already_registered_repos - # @return [Hash] a hash ready to be added to @urls - def repo_to_url(repo) - url = repo.select { |k, _v| URL_KEYS.include?(k) } - status = repo["enabled"] ? @REPO_ENABLED : @REPO_DISABLED - url["new_status"] = status - url - end - - # Removes a repo from the list of repos to use during installation - # - # @param repo [Hash] an entry from @already_registered_repos - def unregister(repo) - @already_registered_repos.delete(repo) - Pkg.SourceDelete(repo["media"]) + # Initialize the package manager and read the current repositories. + # This is only needed in the testing mode to display some reasonable values, + # it reads the data from the current system. + def init_pkg_mgr + # import the pkg-bindings module + Yast.import "Pkg" + Yast.import "PackageCallbacks" + # display progress during refresh + PackageCallbacks.InitPackageCallbacks + # initialize the target + Pkg.TargetInitialize("/") + # load the repository configuration. Refreshes the repositories if needed. + Pkg.SourceRestore + # read the current setup + Y2Packager::OriginalRepositorySetup.instance.read end - # Finds a repo in @already_registered_repos representing the given zypp url - # - # Returns nil if none is found, which means that it always returns nil when - # running the client for the first time (no urls has been previously - # registered as repos). - # - # @param zypp_repo [Hash] an entry from @system_urls - # @return [Hash] repo originated from the url. Nil if none. - def equivalent_repo_for(zypp_repo) - @already_registered_repos.detect { |r| r["alias"] == alias_for(zypp_repo) } - end + # Save the changes to disk, reset the stored old repositories to not display + # this dialog again after going back. + def save_pkg_mgr + # do not save the changes in the test mode + Pkg.SourceSaveAll unless test? - # Alias for an zypp url record - # - # @param zypp_repo [Hash] an entry from @system_urls - # @return [String] valid alias to reference the repository - def alias_for(zypp_repo) - zypp_repo["id"] || zypp_repo["baseurl"] + # clear the old repositories + Y2Packager::OriginalRepositorySetup.instance.repositories.clear end - # Returns an url description based on a zypp url + # Running in a test mode? # - # @param repo [Hash] an entry from @system_urls - # @return [Hash] a hash ready to be added to @urls - def zypp_repo_to_url(repo) - # bnc #300901 - enabled = nil - # mapping url (zypp-based) keys to keys used in pkg-bindings - if Ops.is_integer?(Ops.get_integer(repo, @REPO_ENABLED, 0)) - enabled = Ops.get_integer(repo, @REPO_ENABLED, 0) == 1 - elsif Ops.is_string?(Ops.get_string(repo, @REPO_ENABLED, "0")) - enabled = Ops.get_string(repo, @REPO_ENABLED, "0") == "1" - elsif Ops.is_boolean?( - Ops.get_boolean(repo, @REPO_ENABLED, false) - ) - enabled = Ops.get_boolean(repo, @REPO_ENABLED, false) - end - - # bnc #387261 - autorefresh = true - # mapping url (zypp-based) keys to keys used in pkg-bindings - if Ops.is_integer?(Ops.get_integer(repo, "autorefresh", 0)) - autorefresh = Ops.get_integer(repo, "autorefresh", 0) == 1 - elsif Ops.is_string?(Ops.get_string(repo, "autorefresh", "0")) - autorefresh = Ops.get_string(repo, "autorefresh", "0") == "1" - elsif Ops.is_boolean?( - Ops.get_boolean(repo, "autorefresh", false) - ) - autorefresh = Ops.get_boolean(repo, "autorefresh", false) - end - keeppackages = true - # mapping url (zypp-based) keys to keys used in pkg-bindings - if Ops.is_integer?(Ops.get_integer(repo, "keeppackages", 0)) - keeppackages = Ops.get_integer(repo, "keeppackages", 0) == 1 - elsif Ops.is_string?(Ops.get_string(repo, "keeppackages", "0")) - keeppackages = Ops.get_string(repo, "keeppackages", "0") == "1" - elsif Ops.is_boolean?( - Ops.get_boolean(repo, "keeppackages", false) - ) - keeppackages = Ops.get_boolean(repo, "keeppackages", false) - end - new_url = { - "autorefresh" => autorefresh, - "alias" => alias_for(repo), - "url" => Ops.get(repo, "baseurl"), - "name" => (repo["name"] || repo["id"]), - "keeppackages" => keeppackages, - @REPO_ENABLED => enabled, - "initial_url_status" => @REPO_REMOVED, - "new_status" => @REPO_REMOVED - } - if Builtins.haskey(repo, "priority") - Ops.set( - new_url, - "priority", - Ops.get_integer(repo, "priority", 99) - ) - end - # store the repo-type as well - if Ops.get_string(repo, "type", "") != "" - Ops.set( - new_url, - "type", - Ops.get_string(repo, "type", "") - ) - end - new_url + # @return [Boolean] `true` if running in the test mode, `false` otherwise + def test? + ENV["YAST_TEST"] == "1" end end end diff --git a/src/lib/installation/upgrade_repo_manager.rb b/src/lib/installation/upgrade_repo_manager.rb new file mode 100644 index 000000000..db185400e --- /dev/null +++ b/src/lib/installation/upgrade_repo_manager.rb @@ -0,0 +1,162 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) 2020 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 "y2packager/original_repository_setup" +require "y2packager/repository" +require "y2packager/service" + +Yast.import "Pkg" + +module Installation + # This class takes care of managing the old repositories and services + # during upgrade. It takes care of modifying the old repositories + # and using them in the new upgraded system. + class UpgradeRepoManager + include Yast::Logger + + # @return [Array] The old repositories + attr_reader :repositories + # @return [Array] The old services + attr_reader :services + + # Constructor + # + # @param old_repositories [Array] the old + # repositories which should be managed + # @param old_services [Array] the old + # services which should be managed + def initialize(old_repositories, old_services) + @repositories = old_repositories + @services = old_services + @new_urls = {} + # by default remove all repositories + @status_map = Hash[repositories.map { |r| [r, :removed] }] + end + + def self.create_from_old_repositories + # Find the current repositories for the old ones, + # the repositories might have been changed during the upgrade + # workflow by other clients, this ensures we use the current data. + current_repos = Y2Packager::Repository.all + stored_repos = Y2Packager::OriginalRepositorySetup.instance.repositories + stored_repo_aliases = stored_repos.map(&:repo_alias) + old_repos = current_repos.select { |r| stored_repo_aliases.include?(r.repo_alias) } + + current_services = Y2Packager::Service.all + stored_services = Y2Packager::OriginalRepositorySetup.instance.services + stored_service_aliases = stored_services.map(&:alias) + old_services = current_services.select { |s| stored_service_aliases.include?(s.alias) } + + # sort the repositories by name + new(old_repos.sort_by(&:name), old_services) + end + + # Return the configured status of a repository. + # @param repo [Y2Packager::Repository] the repository + # @return [Symbol, nil] `:removed`, `:enabled` or `:disabled` symbol, + # `nil` if the repository is not known + def repo_status(repo) + status_map[repo] + end + + # Return the repository URL, if it was changed by the user than the new + # URL is returned. + # + # @param repo [Y2Packager::Repository] The queried repository + # @return [String] The URL + def repo_url(repo) + new_urls[repo] || repo.url.to_s + end + + # Toggle the repository status. + # It cycles the repository status in this order: + # Removed->Enabled->Disabled->Removed->Enabled->Disabled->... + # + # @param repo [Y2Packager::Repository] the repository + # @return [Symbol, nil] `:removed`, `:enabled` or `:disabled` symbol, + # `nil` if the repository is not known + def toggle_repo_status(repo) + case repo_status(repo) + when :enabled + status_map[repo] = :disabled + when :disabled + status_map[repo] = :removed + when :removed + status_map[repo] = :enabled + end + end + + # Change the URL of a repository. + # + # @param repo [Y2Packager::Repository] The repository + # @param url [String] Its new URL + def change_url(repo, url) + new_urls[repo] = url if repo.raw_url.to_s != url + end + + # Activate the changes. This will enable/disable the repositories, + # set the new URLs and remove old services without saving the changes. + # To make the changes permanent (saved to disk) call `YaST::Pkg.SourceSaveAll` + # after calling this method. + def activate_changes + update_urls + process_repos + remove_services + end + + private + + # remove the old services + def remove_services + log.info("Old services to remove: #{services.map(&:alias).inspect}") + services.each do |s| + log.info("Removing old service #{s.alias}...") + Yast::Pkg.ServiceDelete(s.alias) + end + end + + # @return [Hash] Maps the repositories + # to the new requested state. + attr_reader :status_map + + # @return [Hash] Maps the repositories + # to the new requested URLs. + attr_reader :new_urls + + # change the status of the repositories to the requested states + def process_repos + status_map.each do |repo, status| + case status + when :enabled + log.info("Enabling #{repo.repo_alias.inspect} ...") + repo.enable! + when :disabled + log.info("Disabling #{repo.repo_alias.inspect} ...") + repo.disable! + when :removed + log.info("Removing #{repo.repo_alias.inspect} ...") + repo.delete! + end + end + end + + # update the repository URLs to the requested values + def update_urls + new_urls.each do |repo, url| + repo.url = url + end + end + end +end diff --git a/test/lib/clients/inst_upgrade_urls_test.rb b/test/lib/clients/inst_upgrade_urls_test.rb new file mode 100755 index 000000000..8b7efec57 --- /dev/null +++ b/test/lib/clients/inst_upgrade_urls_test.rb @@ -0,0 +1,61 @@ +#!/usr/bin/env rspec + +require_relative "../../test_helper" +require "installation/clients/inst_upgrade_urls" + +describe Yast::InstUpgradeUrlsClient do + let(:repo1) do + Y2Packager::Repository.new(repo_id: 1, repo_alias: "test1", + url: "https://example.com/1", raw_url: "https://example.com/1", + name: "repo1", enabled: true, autorefresh: true) + end + + let(:repo2) do + Y2Packager::Repository.new(repo_id: 2, repo_alias: "test2", + url: "https://example.com/2", raw_url: "https://example.com/2", + name: "repo2", enabled: true, autorefresh: true) + end + + let(:service1) do + Y2Packager::Service.new(service_alias: "service1", name: "service1", + url: "https://example.com/service", enabled: true, auto_refresh: true) + end + + let(:repo_mgr) { Installation::UpgradeRepoManager.new([repo1, repo2], [service1]) } + + before do + allow(Yast::GetInstArgs).to receive(:going_back).and_return(false) + allow(Yast::Stage).to receive(:initial).and_return(true) + allow(Yast::Mode).to receive(:update).and_return(true) + allow(Yast::Mode).to receive(:normal).and_return(false) + allow(Yast::Wizard).to receive(:SetContents) + + allow(Installation::UpgradeRepoManager).to receive(:create_from_old_repositories) + .and_return(repo_mgr) + + allow(Yast::UI).to receive(:QueryWidget) + allow(Yast::UI).to receive(:ChangeWidget) + allow(Yast::Pkg).to receive(:SourceSaveAll) + end + + describe "#main" do + before do + allow(repo1).to receive(:delete!) + allow(repo2).to receive(:delete!) + allow(Yast::Pkg).to receive(:ServiceDelete).with(service1.alias) + end + + it "removes the selected repositories after pressing Next" do + expect(Yast::UI).to receive(:UserInput).and_return(:next) + expect(repo1).to receive(:delete!) + expect(repo2).to receive(:delete!) + subject.main + end + + it "removes all old services after pressing Next" do + expect(Yast::UI).to receive(:UserInput).and_return(:next) + expect(Yast::Pkg).to receive(:ServiceDelete).with(service1.alias) + subject.main + end + end +end diff --git a/test/lib/upgrade_repo_manager_test.rb b/test/lib/upgrade_repo_manager_test.rb new file mode 100755 index 000000000..3a64c27d2 --- /dev/null +++ b/test/lib/upgrade_repo_manager_test.rb @@ -0,0 +1,139 @@ +#! /usr/bin/env rspec + +require_relative "../test_helper" +require "installation/upgrade_repo_manager" + +describe Installation::UpgradeRepoManager do + let(:repo1) do + Y2Packager::Repository.new(repo_id: 1, repo_alias: "test1", + url: "https://example.com/1", raw_url: "https://example.com/1", + name: "repo1", enabled: true, autorefresh: true) + end + + let(:repo2) do + Y2Packager::Repository.new(repo_id: 2, repo_alias: "test2", + url: "https://example.com/2", raw_url: "https://example.com/2", + name: "repo2", enabled: true, autorefresh: true) + end + + let(:extra_repo) do + Y2Packager::Repository.new(repo_id: 42, repo_alias: "extra", + url: "https://example.com/extra", raw_url: "https://example.com/extra", + name: "extra", enabled: true, autorefresh: true) + end + + let(:service1) do + Y2Packager::Service.new(service_alias: "service1", name: "service1", + url: "https://example.com/service", enabled: true, auto_refresh: true) + end + + subject { Installation::UpgradeRepoManager.new([repo1, repo2], [service1]) } + + describe "#repo_status" do + it "preselects all repositories to remove" do + expect(subject.repo_status(repo1)).to eq(:removed) + expect(subject.repo_status(repo2)).to eq(:removed) + end + + it "returns `nil` for unknown repositories" do + expect(subject.repo_status(extra_repo)).to be_nil + end + end + + describe "#repo_url" do + it "returns the original raw URL if it has not been changed" do + expect(subject.repo_url(repo1)).to eq(repo1.raw_url) + end + + end + + describe "#change_url" do + it "changes the repository" do + new_url = "https://example.com/new" + subject.change_url(repo1, new_url) + expect(subject.repo_url(repo1)).to eq(new_url) + end + end + + describe "#toggle_repo_status" do + it "changes the :removed status to :enabled" do + expect { subject.toggle_repo_status(repo1) }.to change { subject.repo_status(repo1) } + .from(:removed).to(:enabled) + end + + it "changes the :enabled status to :disabled" do + # call toggle to switch the internal state to the requested value + subject.toggle_repo_status(repo1) + expect { subject.toggle_repo_status(repo1) }.to change { subject.repo_status(repo1) } + .from(:enabled).to(:disabled) + end + + it "changes the :disabled status to :removed" do + # call toggle to switch the internal state to the requested value + subject.toggle_repo_status(repo1) + subject.toggle_repo_status(repo1) + expect { subject.toggle_repo_status(repo1) }.to change { subject.repo_status(repo1) } + .from(:disabled).to(:removed) + end + end + + describe "#activate_changes" do + before do + allow(repo1).to receive(:enable!) + allow(repo1).to receive(:disable!) + allow(repo1).to receive(:delete!) + allow(Yast::Pkg).to receive(:ServiceDelete) + end + + it "removes the selected repositories" do + expect(repo1).to receive(:delete!) + expect(repo2).to receive(:delete!) + subject.activate_changes + end + + it "enables the selected repositories" do + subject.toggle_repo_status(repo1) + expect(repo1).to receive(:enable!) + subject.activate_changes + end + + it "disables the selected repositories" do + subject.toggle_repo_status(repo1) + subject.toggle_repo_status(repo1) + expect(repo1).to receive(:disable!) + subject.activate_changes + end + + it "updates the URLs of changed repositories" do + new_url = "https://example.com/new" + subject.change_url(repo1, new_url) + expect(repo1).to receive(:url=).with(new_url) + subject.activate_changes + end + + it "removes the old services" do + expect(Yast::Pkg).to receive(:ServiceDelete).with(service1.alias) + subject.activate_changes + end + end + + describe ".create_from_old_repositories" do + before do + allow(Y2Packager::Repository).to receive(:all).and_return([repo1, repo2]) + expect(Y2Packager::OriginalRepositorySetup.instance).to receive(:repositories) + .and_return([repo1, repo2]) + end + + it "initializes the UpgradeRepoManager from the stored old repositories" do + old_repo_manager = Installation::UpgradeRepoManager.create_from_old_repositories + expect(old_repo_manager.repositories).to eq([repo1, repo2]) + end + + it "skips the already removed repositories" do + # make only repo1 currently available + allow(Y2Packager::Repository).to receive(:all).and_return([repo1]) + old_repo_manager = Installation::UpgradeRepoManager.create_from_old_repositories + expect(old_repo_manager.repositories).to eq([repo1]) + end + end +end