From efd7e263b983dd124f0aa8010bb017c041b8804f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 2 Nov 2021 12:58:19 +0000 Subject: [PATCH] Add a new API to manage products and packages * The implementation is not finished. * libzypp is the only implemented backend. --- .../packages/src/lib/y2packager/backend.rb | 48 ++++++++++ .../src/lib/y2packager/libzypp_backend.rb | 84 +++++++++++++++++ .../packages/src/lib/y2packager/rpm_repo.rb | 29 ++++++ .../src/lib/y2packager/software_manager.rb | 91 ++++++++++++++++++ .../src/lib/y2packager/software_search.rb | 88 +++++++++++++++++ .../test/y2packager/libzypp_backend_test.rb | 94 +++++++++++++++++++ .../test/y2packager/software_manager_test.rb | 64 +++++++++++++ .../test/y2packager/software_search_test.rb | 76 +++++++++++++++ 8 files changed, 574 insertions(+) create mode 100644 library/packages/src/lib/y2packager/backend.rb create mode 100644 library/packages/src/lib/y2packager/libzypp_backend.rb create mode 100644 library/packages/src/lib/y2packager/rpm_repo.rb create mode 100644 library/packages/src/lib/y2packager/software_manager.rb create mode 100644 library/packages/src/lib/y2packager/software_search.rb create mode 100644 library/packages/test/y2packager/libzypp_backend_test.rb create mode 100644 library/packages/test/y2packager/software_manager_test.rb create mode 100644 library/packages/test/y2packager/software_search_test.rb diff --git a/library/packages/src/lib/y2packager/backend.rb b/library/packages/src/lib/y2packager/backend.rb new file mode 100644 index 000000000..fa1b051aa --- /dev/null +++ b/library/packages/src/lib/y2packager/backend.rb @@ -0,0 +1,48 @@ +# Copyright (c) [2021] SUSE LLC +# +# All Rights Reserved. +# +# 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. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +module Y2Packager + # Implements support for a software management system + # + # To implement support for an additional backend, just inherit from this class + # and implement the corresponding methods (e.g., #probe, #repositories, #search, etc.). + class Backend + # Initializes the backend + def probe; end + + # Returns the list of repositories + # + # @return [Array] + def repositories + [] + end + + # Returns the resolvables according to the given conditions and properties + # + # @todo Return a ResolvablesCollection instance. + # + # @param conditions [Hash] Search conditions (e.g., { name: "SLES" } + # @param properties [Array] List of properties to include in the result. + # The default list is defined by each backend. + # @return [Array] + def search(*) + [] + end + end +end diff --git a/library/packages/src/lib/y2packager/libzypp_backend.rb b/library/packages/src/lib/y2packager/libzypp_backend.rb new file mode 100644 index 000000000..9903fdf1e --- /dev/null +++ b/library/packages/src/lib/y2packager/libzypp_backend.rb @@ -0,0 +1,84 @@ +# Copyright (c) [2021] SUSE LLC +# +# All Rights Reserved. +# +# 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. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require "yast" +require "y2packager/backend" +require "y2packager/rpm_repo" +require "y2packager/package" +require "y2packager/product" + +module Y2Packager + # Backend implementation for libzypp + class LibzyppBackend < Backend + # Initialize the libzypp subsystem using the pkg-bindings + def probe + Yast.import "Pkg" + Yast.import "PackageLock" + Yast::Pkg.TargetInitialize("/") + Yast::Pkg.TargetLoad + Yast::Pkg.SourceRestore + Yast::Pkg.SourceLoad + end + + # Reads the repositories from the system + # + # @return [Array] + def repositories + Yast::Pkg.SourceGetCurrent(false).map do |repo_id| + repo = Yast::Pkg.SourceGeneralData(repo_id) + raise NotFound if repo.nil? + + RpmRepo.new(repo_id: repo_id, repo_alias: repo["alias"], + enabled: repo["enabled"], name: repo["name"], + autorefresh: repo["autorefresh"], url: repo["raw_url"], + product_dir: repo["product_dir"]) + end + end + + # @todo Allow passing multiple statuses + # @todo Use a set of default properties so you do not need to explictly pass them + def search(conditions:, properties:) + resolvables = Yast::Pkg.Resolvables( + conditions, + (properties + [:kind]).uniq + ) + + resolvables.map do |res| + meth = "hash_to_#{res["kind"]}" + next res unless respond_to?(meth, true) + + send(meth, res) + end + end + + private + + def hash_to_product(hsh) + Y2Packager::Product.new( + name: hsh["name"], arch: hsh["arch"], version: hsh["version"] + ) + end + + def hash_to_package(hsh) + Y2Packager::Package.new( + hsh["name"], hsh["source"], hsh["version"] + ) + end + end +end diff --git a/library/packages/src/lib/y2packager/rpm_repo.rb b/library/packages/src/lib/y2packager/rpm_repo.rb new file mode 100644 index 000000000..12a9add9d --- /dev/null +++ b/library/packages/src/lib/y2packager/rpm_repo.rb @@ -0,0 +1,29 @@ +# Copyright (c) [2021] SUSE LLC +# +# All Rights Reserved. +# +# 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. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require "y2packager/repository" + +module Y2Packager + # Represents an RPM packages repository + # + # @todo Move here the RPM specific logic from the Repository class + # and rename Repository to Origin. + class RpmRepo < Repository + end +end diff --git a/library/packages/src/lib/y2packager/software_manager.rb b/library/packages/src/lib/y2packager/software_manager.rb new file mode 100644 index 000000000..b86ecb39c --- /dev/null +++ b/library/packages/src/lib/y2packager/software_manager.rb @@ -0,0 +1,91 @@ +# Copyright (c) [2021] SUSE LLC +# +# All Rights Reserved. +# +# 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. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require "y2packager/repository" +require "y2packager/libzypp_backend" +require "y2packager/software_search" + +module Y2Packager + # This class represents the software management subsystem + # + # It allows managing software repositories, installing/removing software, and so on. + # + # @example Initialize the software management subsystem + # software = SoftwareManagement.new(LibzyppBackend.new) + # software.probe + # + # @example Convenience method to initialize the software manager + # SoftwareManager.probe + # SoftwareManager.current #=> # + # + class SoftwareManager + # @return [Array] List of known backends + attr_reader :backends + + class << self + # Returns a SoftwareManager instance and keeps the reference for the future + # + # @note At this time, it always initializes the system using the LibzyppBackend. + # @return [SoftwareManagement] A SoftwareManagement instance for the current system + def current + @current ||= new([LibzyppBackend.new]) + end + + def reset + @current = nil + end + end + + # @param backends [Array] List of backends to use + def initialize(backends) + @backends = backends + end + + # Initialize the software subsystem + def probe + backends.each(&:probe) + end + + # Commits the changes defined in the software proposal + # + # @param [SoftwareProposal] + def commit(_proposal) + # ask the backends to install the given packages/apps + raise NotImplementedError + end + + # List of repositories from all the backends + # + # @return [Array] Defined repositories from all backends + def repositories + backends.each_with_object([]) do |backend, all| + all.concat(backend.repositories) + end + end + + # Returns a search object which includes all backends + # + # @todo Allow disabling any backend. + # + # @return [SoftwareSearch] + def search + SoftwareSearch.new(*backends) + end + end +end diff --git a/library/packages/src/lib/y2packager/software_search.rb b/library/packages/src/lib/y2packager/software_search.rb new file mode 100644 index 000000000..9facb91f6 --- /dev/null +++ b/library/packages/src/lib/y2packager/software_search.rb @@ -0,0 +1,88 @@ +# Copyright (c) [2021] SUSE LLC +# +# All Rights Reserved. +# +# 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. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +module Y2Packager + # Query the software manager for resolvables (packages, products, applications, + # and so on). + # + # Should we have two different classes? One for the conditions/properties + # and the other one that represents the query itself. See Backend#search for + # an explanation. + # + # The SoftwareSearch contains additional information, like the list of backends. + # + # @example Search by name + # query = SoftwareSearch.new(backend) + # + class SoftwareSearch + include Enumerable + + # @return [Array] Limit the search to these backends + attr_reader :backends + + # @return [Array] Properties to include + attr_reader :properties + + # @return [Hash] A hash describing the conditions (e.g., { + # name: "yast2" }) + attr_reader :conditions + + # attributes required for identifying a resolvable + BASE_ATTRIBUTES = [:kind, :name, :version, :arch, :source].freeze + + def initialize(*backends) + @backends = backends # limit the query to these backends + @properties = BASE_ATTRIBUTES.dup + @conditions = {} + end + + def named(name) + with(name: name) + self + end + + def including(*names) + @properties.concat(names) + self + end + + def excluding(*names) + names.each { |a| @properties.delete(a) } + self + end + + def with(conds = {}) + @conditions.merge!(conds) + self + end + + # @todo Rely on a ResolvablesCollection instead + def each(&block) + resolvables.each(&block) + end + + private + + def resolvables + backends.each_with_object([]) do |backend, all| + all.concat(backend.search(conditions: conditions, properties: properties)) + end + end + end +end diff --git a/library/packages/test/y2packager/libzypp_backend_test.rb b/library/packages/test/y2packager/libzypp_backend_test.rb new file mode 100644 index 000000000..67da40ceb --- /dev/null +++ b/library/packages/test/y2packager/libzypp_backend_test.rb @@ -0,0 +1,94 @@ +# Copyright (c) [2021] SUSE LLC +# +# All Rights Reserved. +# +# 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. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require_relative "../test_helper" +require "y2packager/libzypp_backend" +require "y2packager/software_search" + +Yast.import "Pkg" + +describe Y2Packager::LibzyppBackend do + subject(:backend) { described_class.new } + + describe "#probe" do + it "initializes the packaging system" do + # TODO: this is not the most useful test + expect(Yast::Pkg).to receive(:SourceLoad) + + backend.probe + end + end + + describe "#repositories" do + before do + allow(Yast::Pkg).to receive(:SourceGetCurrent).with(false).and_return([0, 1]) + allow(Yast::Pkg).to receive(:SourceGeneralData).with(0) + .and_return("name" => "SLES", "raw_url" => "file:///sles") + allow(Yast::Pkg).to receive(:SourceGeneralData).with(1) + .and_return("name" => "SLED", "raw_url" => "file:///sled") + end + + it "returns the list of known repositories" do + repos = backend.repositories + expect(repos[0]).to be_a(Y2Packager::RpmRepo) + expect(repos[0].name).to eq("SLES") + expect(repos[1]).to be_a(Y2Packager::RpmRepo) + expect(repos[1].name).to eq("SLED") + end + end + + describe "#search" do + context "searching a package by name" do + it "returns the package with the given name" do + expect(Yast::Pkg).to receive(:Resolvables) + .with({ kind: :package, name: "yast2" }, [:name, :version, :kind]) + .and_return([ + { "name" => "yast2", "version" => "4.4.0", "source" => 1, "kind" => :package } + ]) + + pkg = backend.search( + conditions: { kind: :package, name: "yast2" }, properties: [:name, :version] + ).first + expect(pkg).to be_a(Y2Packager::Package) + expect(pkg.name).to eq("yast2") + expect(pkg.version).to eq("4.4.0") + expect(pkg.repo_id).to eq(1) + end + end + + context "searching a product by name" do + it "returns the product with the given name" do + expect(Yast::Pkg).to receive(:Resolvables) + .with( + { kind: :product, name: "openSUSE" }, [:name, :version, :kind] + ) + .and_return([ + { "name" => "openSUSE", "version" => "20211027-0", "kind" => :product } + ]) + + prod = backend.search( + conditions: { kind: :product, name: "openSUSE" }, properties: [:name, :version] + ).first + expect(prod).to be_a(Y2Packager::Product) + expect(prod.name).to eq("openSUSE") + expect(prod.version).to eq("20211027-0") + end + end + end +end diff --git a/library/packages/test/y2packager/software_manager_test.rb b/library/packages/test/y2packager/software_manager_test.rb new file mode 100644 index 000000000..3223c4beb --- /dev/null +++ b/library/packages/test/y2packager/software_manager_test.rb @@ -0,0 +1,64 @@ +# Copyright (c) [2021] SUSE LLC +# +# All Rights Reserved. +# +# 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. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require_relative "../test_helper" +require "y2packager/software_manager" + +describe Y2Packager::SoftwareManager do + subject(:software) do + described_class.new([backend]) + end + + let(:backend) do + instance_double(Y2Packager::LibzyppBackend, repositories: [repo]) + end + + let(:repo) do + instance_double(Y2Packager::Repository) + end + + describe ".current" do + before { described_class.reset } + + it "creates a SoftwareManagement instance" do + expect(described_class.current).to be_a(Y2Packager::SoftwareManager) + end + + context "when called twice" do + it "returns the same instance" do + software0 = described_class.current + software1 = described_class.current + expect(software0).to be(software1) + end + end + end + + describe "#repositories" do + it "returns repositories from all backends" do + expect(software.repositories).to eq([repo]) + end + end + + describe "#probe" do + it "calls the probe method of each backend" do + expect(backend).to receive(:probe) + software.probe + end + end +end diff --git a/library/packages/test/y2packager/software_search_test.rb b/library/packages/test/y2packager/software_search_test.rb new file mode 100644 index 000000000..361b588ff --- /dev/null +++ b/library/packages/test/y2packager/software_search_test.rb @@ -0,0 +1,76 @@ +# Copyright (c) [2021] SUSE LLC +# +# All Rights Reserved. +# +# 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. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require_relative "../test_helper" +require "y2packager/software_search" +require "y2packager/backend" + +describe Y2Packager::SoftwareSearch do + subject(:search) { described_class.new(backend) } + + let(:backend) do + instance_double(Y2Packager::Backend) + end + + describe "#with" do + it "sets conditions" do + search.with(name: "yast2-packager") + expect(search.conditions).to eq(name: "yast2-packager") + end + end + + describe "#named" do + it "sets a condition on the name" do + search.named("yast2") + expect(search.conditions).to eq(name: "yast2") + end + end + + describe "#including" do + it "adds a property to the list of properties to include" do + expect(search.properties).to_not include(:description) + search.including(:description) + expect(search.properties).to include(:description) + end + end + + describe "#excluding" do + it "adds a property to the list of properties to exclude" do + expect(search.properties).to include(:name) + search.excluding(:name) + expect(search.properties).to_not include(:name) + end + end + + describe "#to_a" do + let(:package) { double("yast2") } + + it "asks the backend an returns the result" do + search.with(name: "SLES") + + expect(backend).to receive(:search) + .with( + conditions: { name: "SLES" }, + properties: array_including(:kind, :name, :version, :arch, :source) + ) + .and_return([package]) + expect(search.to_a).to eq([package]) + end + end +end