Skip to content

Commit

Permalink
fix(zypp): Reuse the repositories, use libzypp in "chroot" (#1329)
Browse files Browse the repository at this point in the history
## Problem

- When changing the product from openSUSE Tumleweed to openSUSE MicroOS
the Tumbleweed repository is refreshed again
- The code initializes the libzypp in the Live ISO system, there are
some workarounds to avoid reusing the Live ISO repositories

Related cards:
-
https://trello.com/c/liPX29Cu/3695-5-research-improve-software-management-performance
 

## Solution

- When the changed product uses the same repositories then just reuse
them, do not create the repositories again
- Run libzypp in `/run/agama/zypp` repository to not mess with the
package management from the Live ISO, removed the related workarounds

## Notes

- The reusing is simple all-or-nothing, i.e. the products must use the
very same repositories. In the future we could improve it to allow
reusing just a subset of the repositories.
- Maybe it will need some adoption when the system is registered, but as
there is no real product to register I'm leaving this for the future.
- I put the zypp lock to `/run/agama/zypp` as well, that means now you
can run `zypper` in the Live system while Agama is running, yay!
:star_struck:
- That workaround with switching `Yast::Stage` is not needed anymore, it
was needed when we run the proposal using the YaST code, it is not
needed with the new refactored code.

## Testing

- Tested manually (mvidner: in container, switched products 3 times,
without hitting Install)
  • Loading branch information
lslezak authored Jun 13, 2024
2 parents 3df8883 + 8453f58 commit 4c093fd
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 82 deletions.
74 changes: 36 additions & 38 deletions service/lib/agama/software/manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
Yast.import "Packages"
Yast.import "PackageCallbacks"
Yast.import "Pkg"
Yast.import "Stage"

module Agama
module Software
Expand Down Expand Up @@ -72,6 +71,11 @@ class Manager # rubocop:disable Metrics/ClassLength
PROPOSAL_ID = "agama-user-software-selection"
private_constant :PROPOSAL_ID

# create the libzypp lock and the zypp caches in a special directory to
# not be affected by the Live system package management
TARGET_DIR = "/run/agama/zypp"
private_constant :TARGET_DIR

attr_accessor :languages

# Available products for installation.
Expand All @@ -97,6 +101,7 @@ def initialize(config, logger)
@user_patterns = []
@selected_patterns_change_callbacks = []
on_progress_change { logger.info(progress.to_s) }
initialize_target
end

# Selects a product with the given id.
Expand All @@ -112,8 +117,10 @@ def select_product(id)

raise ArgumentError unless new_product

update_repositories(new_product)

@product = new_product
repositories.delete_all

update_issues
true
end
Expand All @@ -124,14 +131,9 @@ def probe

logger.info "Probing software"

# as we use liveDVD with normal like ENV, lets temporary switch to normal to use its repos
Yast::Stage.Set("normal")

if repositories.empty?
start_progress(4)
store_original_repos
start_progress(3)
Yast::PackageCallbacks.InitPackageCallbacks(logger)
progress.step(_("Initializing target repositories")) { initialize_target_repos }
progress.step(_("Initializing sources")) { add_base_repos }
else
start_progress(2)
Expand All @@ -140,12 +142,16 @@ def probe
progress.step(_("Refreshing repositories metadata")) { repositories.load }
progress.step(_("Calculating the software proposal")) { propose }

Yast::Stage.Set("initial")
update_issues
end

def initialize_target_repos
Yast::Pkg.TargetInitialize("/")
def initialize_target
# create the zypp lock also in the target directory
ENV["ZYPP_LOCKFILE_ROOT"] = TARGET_DIR
# cleanup the previous content (after service restart or crash)
FileUtils.rm_rf(TARGET_DIR)
FileUtils.mkdir_p(TARGET_DIR)
Yast::Pkg.TargetInitialize(TARGET_DIR)
import_gpg_keys
end

Expand Down Expand Up @@ -186,14 +192,13 @@ def install

# Writes the repositories information to the installed system
def finish
start_progress(2)
start_progress(1)
progress.step(_("Writing repositories to the target system")) do
Yast::Pkg.SourceSaveAll
Yast::Pkg.TargetFinish
Yast::Pkg.SourceCacheCopyTo(Yast::Installation.destdir)
registration.finish
end
progress.step(_("Restoring original repositories")) { restore_original_repos }
end

# Determine whether the given tag is provided by the selected packages
Expand Down Expand Up @@ -415,31 +420,6 @@ def add_base_repos
product.repositories.each { |url| repositories.add(url) }
end

REPOS_BACKUP = "/etc/zypp/repos.d.agama.backup"
private_constant :REPOS_BACKUP

REPOS_DIR = "/etc/zypp/repos.d"
private_constant :REPOS_DIR

# ensure that repos backup is there and repos.d is empty
def store_original_repos
# Backup was already created, so just remove all repos
if File.directory?(REPOS_BACKUP)
logger.info "removing #{REPOS_DIR}"
FileUtils.rm_rf(REPOS_DIR)
else # move repos to backup
logger.info "moving #{REPOS_DIR} to #{REPOS_BACKUP}"
FileUtils.mv(REPOS_DIR, REPOS_BACKUP)
end
end

def restore_original_repos
logger.info "removing #{REPOS_DIR}"
FileUtils.rm_rf(REPOS_DIR)
logger.info "moving #{REPOS_BACKUP} to #{REPOS_DIR}"
FileUtils.mv(REPOS_BACKUP, REPOS_DIR)
end

# Adds resolvables for selected product
def select_resolvables
proposal.set_resolvables("agama", :pattern, product.mandatory_patterns)
Expand Down Expand Up @@ -514,6 +494,24 @@ def missing_registration?
def pattern_exist?(pattern_name)
!Y2Packager::Resolvable.find(kind: :pattern, name: pattern_name).empty?
end

# update the zypp repositories for the new product, either delete them
# or keep them untouched
# @param new_product [Agama::Software::Product] the new selected product
def update_repositories(new_product)
# reuse the repositories when they are the same as for the previously
# selected product
# TODO: what about registered products?
# TODO: allow a partial match? i.e. keep the same repositories, delete
# additional repositories and add missing ones
if product&.repositories&.sort == new_product.repositories.sort
# the same repositories, we just needed to reset the package selection
Yast::Pkg.PkgReset()
else
# delete all, the #probe call will add the new repos
repositories.delete_all
end
end
end
end
end
60 changes: 16 additions & 44 deletions service/test/agama/software/manager_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
disabled: disabled_repos
)
end
let(:products) { [] }

let(:proposal) do
instance_double(
Expand Down Expand Up @@ -126,6 +127,20 @@
expect(manager.product.id).to eq("test1")
end
end

context "when GPG keys are available at /" do
let(:gpg_keys) { ["/usr/lib/gnupg/keys/gpg-key.asc"] }

it "imports the GPG keys" do
expect(Yast::Pkg).to receive(:ImportGPGKey).with(gpg_keys.first, true)
described_class.new(config, logger)
end
end

it "initializes the package system" do
expect(Yast::Pkg).to receive(:TargetInitialize)
described_class.new(config, logger)
end
end

shared_examples "software issues" do |tested_method|
Expand Down Expand Up @@ -184,35 +199,10 @@
end

describe "#probe" do
let(:rootdir) { Dir.mktmpdir }
let(:repos_dir) { File.join(rootdir, "etc", "zypp", "repos.d") }
let(:backup_repos_dir) { File.join(rootdir, "etc", "zypp", "repos.d.backup") }

before do
stub_const("Agama::Software::Manager::REPOS_DIR", repos_dir)
stub_const("Agama::Software::Manager::REPOS_BACKUP", backup_repos_dir)
FileUtils.mkdir_p(repos_dir)
subject.select_product("Tumbleweed")
end

after do
FileUtils.remove_entry(rootdir)
end

it "initializes the package system" do
expect(Yast::Pkg).to receive(:TargetInitialize).with("/")
subject.probe
end

context "when GPG keys are available at /" do
let(:gpg_keys) { ["/usr/lib/gnupg/keys/gpg-key.asc"] }

it "imports the GPG keys" do
expect(Yast::Pkg).to receive(:ImportGPGKey).with(gpg_keys.first, true)
subject.probe
end
end

it "creates a packages proposal" do
expect(proposal).to receive(:calculate)
subject.probe
Expand Down Expand Up @@ -351,31 +341,13 @@
end

describe "#finish" do
let(:rootdir) { Dir.mktmpdir }
let(:repos_dir) { File.join(rootdir, "etc", "zypp", "repos.d") }
let(:backup_repos_dir) { File.join(rootdir, "etc", "zypp", "repos.d.backup") }

before do
stub_const("Agama::Software::Manager::REPOS_DIR", repos_dir)
stub_const("Agama::Software::Manager::REPOS_BACKUP", backup_repos_dir)
FileUtils.mkdir_p(repos_dir)
FileUtils.mkdir_p(backup_repos_dir)
FileUtils.touch(File.join(backup_repos_dir, "example.repo"))
puts Dir[File.join(repos_dir, "**", "*")]
end

after do
FileUtils.remove_entry(rootdir)
end

it "releases the packaging system and restores the backup" do
it "releases the packaging system" do
expect(Yast::Pkg).to receive(:SourceSaveAll)
expect(Yast::Pkg).to receive(:TargetFinish)
expect(Yast::Pkg).to receive(:SourceCacheCopyTo)
.with(Yast::Installation.destdir)

subject.finish
expect(File).to exist(File.join(repos_dir, "example.repo"))
end
end

Expand Down

0 comments on commit 4c093fd

Please sign in to comment.