library/packages/src/lib/y2packager/release_notes_fetchers/rpm.rb
# ------------------------------------------------------------------------------
# Copyright (c) 2017 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.
# ------------------------------------------------------------------------------
require "yast"
require "fileutils"
require "y2packager/package"
require "y2packager/release_notes"
require "y2packager/release_notes_content_prefs"
require "y2packager/release_notes_fetchers/base"
require "y2packager/resolvable"
require "packages/package_downloader"
require "tmpdir"
Yast.import "Directory"
Yast.import "Pkg"
module Y2Packager
module ReleaseNotesFetchers
# This class is able to read release notes from a RPM package
#
# Release notes for a product are available in a specific package which provides
# "release-notes()" for the given product. For instance, a package which provides
# "release-notes() = SLES" will provide release notes for the SLES product.
#
# If more than one product provide release notes for that product, the first
# one in alphabetical order will be selected.
#
# This reader takes care of downloading the release notes package (if any),
# extracting its content and returning release notes for a given language/format.
#
# @see Base
class Rpm < Base
# Get release notes for the given product
#
# Release notes are downloaded and extracted to work directory. When
# release notes for a language "xx_XX" are not found, it will fallback to
# "xx".
#
# @param prefs [ReleaseNotesContentPrefs] Content preferences
# @return [String,nil] Release notes or nil if a release notes were not found
# (no package providing release notes or notes not found in the package)
def release_notes(prefs)
if release_notes_package.nil?
log.info "No package containing release notes for #{product.name} was found"
return nil
end
relnotes = extract_release_notes(prefs)
log.info "Got release notes for #{product.name} from RPM " \
"#{release_notes_package.name} #{release_notes_package.version} " \
"with #{prefs}"
relnotes
end
# Return release notes latest version identifier
#
# @example Getting release notes version
# reader = ReleaseNotesFetchers::Rpm.new(product)
# reader.latest_version # => "15.0"
#
# @example Not defined product
# reader = ReleaseNotesFetchers::Rpm.new(product)
# reader.latest_version # => :none
#
# @return [String,:none] Package version; :none if no release notes package
# was found.
def latest_version
return :none if release_notes_package.nil?
release_notes_package.version
end
private
# Return the release notes package for a given product
#
# This method queries libzypp asking for the package which contains release
# notes for the given product. It relies on the `release-notes()` tag.
# If more than one product provide release notes for that product, the first
# one in alphabetical order will be selected.
#
# @return [Package,nil] Package containing the release notes; nil if not found
def release_notes_package
return @release_notes_package if @release_notes_package
log.info "Searching release notes for product #{product.name}..."
provides = Yast::Pkg.PkgQueryProvides("release-notes()")
release_notes_packages = provides.map(&:first).uniq
package_name = release_notes_packages.sort.find do |name|
package_list = Y2Packager::Resolvable.find(kind: :package, name: name)
log.debug "Evaluating #{name} provides: #{package_list}"
package = package_list.find { |p| p.status == :selected } || package_list.find { |p| p.status == :available }
log.debug "Checking package #{package}"
next false unless package
dependencies = package.deps
dependencies.any? do |dep|
# mind $ at the end of the regexp, otherwise for SLES you may get RNs of any SLES.* product
dep["provides"].to_s.match(/^\s*release-notes\(\)\s*=\s*#{Regexp.escape(product.name)}\s*$/)
end
end
log.info "Found release package: #{package_name}"
return nil if package_name.nil?
@release_notes_package = find_package(package_name)
end
# Find the latest available/selected package containing release notes
#
# @return [Package,nil] Package containing release notes; nil if not found
def find_package(name)
Y2Packager::Package.last_version(name)
end
# Return release notes instance
#
# @param prefs [ReleaseNotesContentPrefs] Content preferences
# @return [ReleaseNotes,nil] Release notes for given arguments
def extract_release_notes(prefs)
content, lang = release_notes_content(release_notes_package, prefs)
return nil if content.nil?
Y2Packager::ReleaseNotes.new(
product_name: product.name,
content: content,
user_lang: prefs.user_lang,
lang: lang,
format: prefs.format,
version: release_notes_package.version
)
end
# Return release notes content for a package, language and format
#
# Release notes are downloaded and extracted to work directory. When
# release notes for a language "xx_XX" are not found, it will fallback to
# "xx".
#
# @param package [String] Release notes package name
# @param prefs [ReleaseNotesContentPrefs] Content preferences
# @return [Array<String,String>] Array containing content and language code
# @see release_notes_file
def release_notes_content(package, prefs)
tmpdir = Dir.mktmpdir
begin
package.extract_to(tmpdir)
file, lang = release_notes_file(tmpdir, prefs)
file ? [File.read(file), lang] : nil
ensure
FileUtils.remove_entry_secure(tmpdir)
end
end
# Return release notes file path for a given package, language and format
#
# Release notes are downloaded and extracted to work directory. When
# release notes for a language "xx_XX" are not found, it will fallback to
# "xx".
#
# @param directory [String] Directory where release notes were uncompressed
# @param prefs [ReleaseNotesContentPrefs] Content preferences
# @return [Array<String,String>] Array containing path and language code
def release_notes_file(directory, prefs)
langs = [prefs.user_lang]
langs << prefs.user_lang.split("_", 2).first if prefs.user_lang.include?("_")
langs << prefs.fallback_lang
path = nil
langs.each do |lang|
path = Dir.glob(
File.join(directory, "**", "RELEASE-NOTES.#{lang}.#{prefs.format}")
).first
break unless path.nil?
end
return nil if path.nil?
[path, path[/RELEASE-NOTES\.(.+)\.#{prefs.format}\z/, 1]] if path
end
end
end
end