Skip to content

Commit

Permalink
Added PackageDownloader and PackageExtractor classes (fate#320772)
Browse files Browse the repository at this point in the history
for sharing package downloading and extracting functionality
  • Loading branch information
lslezak committed Mar 10, 2017
1 parent b1954c0 commit 442a759
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 1 deletion.
1 change: 0 additions & 1 deletion .gitignore
Expand Up @@ -45,7 +45,6 @@ library/packages/src/packages

# Package
package/yast2-*.tar.bz2
package/yast2.spec

# other
home:*:*
Expand Down
77 changes: 77 additions & 0 deletions library/packages/src/lib/packages/package_downloader.rb
@@ -0,0 +1,77 @@
# encoding: utf-8
# ------------------------------------------------------------------------------
# Copyright (c) 2017 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 "tempfile"
require "shellwords"

require "yast2/execute"

Yast.import "Pkg"

module Yast2
# Represents a update repository to be used during self-update
#
# @example Downloading a package
# begin
# downloader = PackageDownloader.new(3, "yast2")
# tmp = Tempfile.new("downloaded-package-")
# downloader.download(tmp.path)
# # do something with the package...
# ensure
# tmp.close
# tmp.unlink
# end
#
class PackageDownloader
include Yast::Logger
include Yast::I18n

# @return [Integer] Repository ID
attr_reader :repo_id
# @return [String] Name of the package
attr_reader :package_name

# Error while downloading the package.
class FetchError < StandardError; end

# Constructor
#
# @param [Integer] repo_id Repository ID
# @param [String] package_name Name of the package to download
def initialize(repo_id, package_name)
textdomain "base"

@repo_id = repo_id
@package_name = package_name
end

# Download the package locally to the given path.
#
# It is responsibility of the caller to remove the downloaded package
# when it is not needed anymore.
#
# @param [#to_s] path path where the downloaded package will be stored
#
# @raise PackageNotFound
def download(path)
log.info("Downloading package #{package_name} from repo #{repo_id} to #{path}")
if !Yast::Pkg.ProvidePackage(repo_id, package_name, path.to_s)
log.error("Package #{package_name} could not be retrieved.")
raise FetchError
end
end
end
end
72 changes: 72 additions & 0 deletions library/packages/src/lib/packages/package_extractor.rb
@@ -0,0 +1,72 @@
# encoding: utf-8
# ------------------------------------------------------------------------------
# Copyright (c) 2017 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 "shellwords"

require "yast"
require "yast2/execute"

module Yast2
# Extracts the RPM package contents to a directory.
#
# @example Extracting a package into a temporary directory
# extractor = PackageExtractor("./my_package-0.1-0.noarch.rpm")
# Dir.mktmpdir do |tmpdir|
# extractor.extract(tmpdir)
# # do something with the content in tmpdir...
# end
class PackageExtractor
include Yast::Logger

# Path to the package to extract.
# @return [String] package path
attr_reader :package_path

# The package could not be extracted
class ExtractionFailed < StandardError; end

# Constructor
#
# @param [String] package_path the path to the package to extract
def initialize(package_path)
@package_path = package_path
end

# Command to extract an RPM, the contents is extracted into the current
# working directory.
EXTRACT_CMD = "rpm2cpio %<source>s | cpio --quiet --sparse -dimu --no-absolute-filenames".freeze

# Extracts the RPM contents to the given directory.
#
# It is responsibility of the caller to remove the downloaded package
# when it is not needed anymore.
#
# @param dir [String] Directory where the RPM contents will be extracted to
#
# @raise ExtractionFailed
def extract(dir)
Dir.chdir(dir) do
cmd = format(EXTRACT_CMD, source: Shellwords.escape(package_path))
log.info("Extracting package #{package_path} to #{dir}...")

# we need a shell to process the pipe,
# the "allowed_exitstatus" option forces Cheetah to return the exit code
ret = Yast::Execute.locally("sh", "-c", cmd, allowed_exitstatus: 0..255)
log.info("Extraction result: #{ret}")

raise ExtractionFailed unless ret.zero?
end
end
end
end
Binary file not shown.
44 changes: 44 additions & 0 deletions library/packages/test/data/rpm/dummy_package.spec
@@ -0,0 +1,44 @@
#
# spec file for package dummy_package
#
# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany.
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.

# This is just a testing dummy package for verifying the package
# extraction functionality, it only contains a single testing text file.
#
# Run "rpmbuild -bb dummy_package.spec" to build the package.

Name: dummy_package

Version: 0.1
Release: 0
Summary: A dummy package
License: MIT
Group: Metapackages
BuildRoot: %{_tmppath}/%{name}-%{version}-build
BuildArch: noarch

%description
This is just a dummy package for testing.

%prep

%install
# install a dummy test file
mkdir -p $RPM_BUILD_ROOT/%{_prefix}/share/doc/packages/%{name}
echo "just a testing dummy package" > $RPM_BUILD_ROOT/%{_prefix}/share/doc/packages/%{name}/test

%files
%defattr(644,root,root,755)
%doc %dir %{_prefix}/share/doc/packages/%{name}
%doc %{_prefix}/share/doc/packages/%{name}/

27 changes: 27 additions & 0 deletions library/packages/test/package_downloader_test.rb
@@ -0,0 +1,27 @@
#!/usr/bin/env rspec

require_relative "test_helper"

require "packages/package_downloader"

describe Yast2::PackageDownloader do
Yast.import "Pkg"

let(:repo_id) { 1 }
let(:package) { "package_to_download" }
let(:path) { "dummy" }

subject { Yast2::PackageDownloader.new(repo_id, package) }

describe "#download" do
it "downloads the requested package" do
expect(Yast::Pkg).to receive(:ProvidePackage).with(repo_id, package, path).and_return(true)
subject.download(path)
end

it "raises FetchError when download fails" do
expect(Yast::Pkg).to receive(:ProvidePackage).with(repo_id, package, path).and_return(nil)
expect { subject.download(path) }.to raise_error(Yast2::PackageDownloader::FetchError)
end
end
end
35 changes: 35 additions & 0 deletions library/packages/test/package_extractor_test.rb
@@ -0,0 +1,35 @@
#!/usr/bin/env rspec

require "tempfile"
require_relative "test_helper"
require "packages/package_extractor"

describe Yast2::PackageExtractor do
# a testing RPM package
let(:dummy_package_path) { File.expand_path("../data/rpm/dummy_package-0.1-0.noarch.rpm", __FILE__)}
# the testing file in the package
let(:dummy_file) { "usr/share/doc/packages/dummy_package/test" }
# the contents of the testing file
let(:dummy_file_contents) { "just a testing dummy package\n" }

describe "#extract" do
it "extracts the package" do
Dir.mktmpdir do |tmpdir|
extractor = Yast2::PackageExtractor.new(dummy_package_path)
extractor.extract(tmpdir)

# check the extracted content
extracted = File.join(tmpdir, dummy_file)
expect(File.file?(extracted)).to be(true)
expect(File.read(extracted)).to eq(dummy_file_contents)
end
end

it "raises ExtractionFailed when the extraction fails" do
Dir.mktmpdir do |tmpdir|
extractor = Yast2::PackageExtractor.new("non-existing-package")
expect {extractor.extract(tmpdir)}.to raise_error(Yast2::PackageExtractor::ExtractionFailed)
end
end
end
end
9 changes: 9 additions & 0 deletions package/yast2.spec
Expand Up @@ -47,6 +47,11 @@ BuildRequires: yast2-pkg-bindings >= 2.20.3
BuildRequires: yast2-ruby-bindings >= 3.1.36
BuildRequires: yast2-testsuite
BuildRequires: yast2-ycp-ui-bindings >= 3.1.8
# for the PackageExtractor tests, just make sure they are present,
# these should be installed in the default build anyway
BuildRequires: rpm
BuildRequires: cpio

# for ag_tty (/bin/stty)
# for /usr/bin/md5sum
Requires: coreutils
Expand Down Expand Up @@ -97,6 +102,10 @@ Requires: idnkit
Requires: bind-utils
%endif
Obsoletes: yast2-devel-doc
# for the PackageExtractor class, just make sure they are present,
# these should be present even in a very minimal installation
Requires: rpm
Requires: cpio

%description
This package contains scripts and data needed for SUSE Linux
Expand Down

0 comments on commit 442a759

Please sign in to comment.