[pve-devel] [pbs-devel] [PATCH v6 proxmox-apt 06/11] add release_upgrade function and constants for the current and upgrade suite
Fabian Ebner
f.ebner at proxmox.com
Fri Jun 18 08:50:55 CEST 2021
Am 17.06.21 um 16:16 schrieb Fabian Grünbichler:
> On June 11, 2021 1:43 pm, Fabian Ebner wrote:
>> useful for major upgrades. The stable branch can enable the upgrade, and bump
>> the minor version, while the master branch will adapt to the new release and
>> bump the major version. Each product can depend on the the new major version
>> after branching off the stable branch, and once the release is out, its stable
>> branch can depend on the new minor version.
>>
>> Signed-off-by: Fabian Ebner <f.ebner at proxmox.com>
>> ---
>>
>> Changes from v5:
>> * Make function less general (only care about the current release upgrade)
>> and handle special case for security repository.
>> * Make list of suite available as constants.
>> * Get the current release from /etc/os-release and abort if it is not the
>> same as STABLE_SUITE.
>> * Add a constant UPGRADE_SUITE which can be set for the library's last
>> release in the stable-X branch to enable the release_upgrade() function.
>>
>> .gitignore | 1 +
>> src/repositories/check.rs | 57 +++++++++++++-----------
>> src/repositories/mod.rs | 92 +++++++++++++++++++++++++++++++++++++++
>> tests/repositories.rs | 79 ++++++++++++++++++++++++++++++++-
>> 4 files changed, 202 insertions(+), 27 deletions(-)
>>
>> diff --git a/.gitignore b/.gitignore
>> index db6f13e..de68da9 100644
>> --- a/.gitignore
>> +++ b/.gitignore
>> @@ -1,6 +1,7 @@
>> Cargo.lock
>> target/
>> tests/sources.list.d.actual
>> +tests/sources.list.d.upgraded.actual
>> tests/sources.list.d.digest
>> proxmox-apt-*/
>> *proxmox-apt*.buildinfo
>> diff --git a/src/repositories/check.rs b/src/repositories/check.rs
>> index 585c28d..e0ec93e 100644
>> --- a/src/repositories/check.rs
>> +++ b/src/repositories/check.rs
>> @@ -5,8 +5,34 @@ use crate::types::{
>> APTRepositoryPackageType,
>> };
>>
>> +/// The (code)names of old Debian releases.
>> +pub const OLD_SUITES: [&str; 7] = [
>> + "lenny",
>> + "squeeze",
>> + "wheezy",
>> + "jessie",
>> + "stretch",
>> + "oldoldstable",
>> + "oldstable",
>> +];
>> +
>> +/// The codename of the current stable Debian release.
>> +pub const STABLE_SUITE: &str = "buster";
>> +/// The codename of the next stable Debian release.
>> +pub const NEXT_STABLE_SUITE: &str = "bullseye";
>> +
>> +/// The (code)names of new/testing Debian releases.
>> +pub const NEW_SUITES: [&str; 6] = [
>> + "bookworm",
>> + "trixie",
>> + "testing",
>> + "unstable",
>> + "sid",
>> + "experimental",
>> +];
>> +
>> /// Splits the suite into its base part and variant.
>> -fn suite_variant(suite: &str) -> (&str, &str) {
>> +pub fn suite_variant(suite: &str) -> (&str, &str) {
>> let variants = ["-backports-sloppy", "-backports", "-updates", "/updates"];
>>
>> for variant in variants.iter() {
>> @@ -19,7 +45,7 @@ fn suite_variant(suite: &str) -> (&str, &str) {
>> }
>>
>> /// Get the host part from a given URI.
>> -fn host_from_uri(uri: &str) -> Option<&str> {
>> +pub fn host_from_uri(uri: &str) -> Option<&str> {
>> if let Some(begin) = uri.find("://") {
>> let mut host = uri.split_at(begin + 3).1;
>>
>> @@ -145,34 +171,13 @@ impl APTRepository {
>> /// Checks if old or unstable suites are configured and also that the
>> /// `stable` keyword is not used.
>> fn check_suites(&self, add_info: &mut dyn FnMut(String, String)) {
>> - let old_suites = [
>> - "lenny",
>> - "squeeze",
>> - "wheezy",
>> - "jessie",
>> - "stretch",
>> - "oldoldstable",
>> - "oldstable",
>> - ];
>> -
>> - let next_suite = "bullseye";
>> -
>> - let new_suites = [
>> - "bookworm",
>> - "trixie",
>> - "testing",
>> - "unstable",
>> - "sid",
>> - "experimental",
>> - ];
>> -
>> if self
>> .types
>> .iter()
>> .any(|package_type| *package_type == APTRepositoryPackageType::Deb)
>> {
>> for suite in self.suites.iter() {
>> - if old_suites
>> + if OLD_SUITES
>> .iter()
>> .any(|base_suite| suite_variant(suite).0 == *base_suite)
>> {
>> @@ -182,14 +187,14 @@ impl APTRepository {
>> );
>> }
>>
>> - if suite_variant(suite).0 == next_suite {
>> + if suite_variant(suite).0 == NEXT_STABLE_SUITE {
>> add_info(
>> "ignore-pre-upgrade-warning".to_string(),
>> format!("suite '{}' should not be used in production!", suite),
>> );
>> }
>>
>> - if new_suites
>> + if NEW_SUITES
>> .iter()
>> .any(|base_suite| suite_variant(suite).0 == *base_suite)
>> {
>> diff --git a/src/repositories/mod.rs b/src/repositories/mod.rs
>> index 2c01011..eceede3 100644
>> --- a/src/repositories/mod.rs
>> +++ b/src/repositories/mod.rs
>> @@ -1,4 +1,5 @@
>> use std::collections::BTreeMap;
>> +use std::io::{BufRead, BufReader};
>> use std::path::PathBuf;
>>
>> use anyhow::{bail, format_err, Error};
>> @@ -21,6 +22,11 @@ mod writer;
>> const APT_SOURCES_LIST_FILENAME: &str = "/etc/apt/sources.list";
>> const APT_SOURCES_LIST_DIRECTORY: &str = "/etc/apt/sources.list.d/";
>>
>> +/// The codename of the current stable Debian release.
>> +pub const STABLE_SUITE: &str = check::STABLE_SUITE;
>> +/// The codename of the next stable Debian release or `None` if an upgrade is not yet possible.
>> +pub const UPGRADE_SUITE: Option<&str> = None;
>> +
>> impl APTRepository {
>> /// Crates an empty repository.
>> fn new(file_type: APTRepositoryFileType) -> Self {
>> @@ -265,6 +271,92 @@ pub fn repositories() -> Result<(Vec<APTRepositoryFile>, Vec<APTRepositoryFileEr
>> Ok((files, errors))
>> }
>>
>> +/// Read the `VERSION_CODENAME` from `/etc/os-release`.
>> +fn get_release_codename() -> Result<String, Error> {
>> + let raw = std::fs::read("/etc/os-release")
>> + .map_err(|err| format_err!("unable to read '/etc/os-release' - {}", err))?;
>> +
>> + let reader = BufReader::new(&*raw);
>> +
>> + for line in reader.lines() {
>> + let line = line.map_err(|err| format_err!("unable to read '/etc/os-release' - {}", err))?;
>> +
>> + if let Some(codename) = line.strip_prefix("VERSION_CODENAME=") {
>> + let codename = codename.trim_matches(&['"', '\''][..]);
>> + return Ok(codename.to_string());
>> + }
>> + }
>> +
>> + bail!("unable to parse codename from '/etc/os-release'");
>> +}
>> +
>> +/// For enabled repositories, replaces each occurence of the `STABLE_SUITE` with the
>> +/// `UPGRADE_SUITE` suite, including variants (e.g. `-updates`).
>> +///
>> +/// Returns an error if the `UPGRADE_SUITE` is currently `None`, i.e. upgrade not yet possible.
>> +///
>> +/// Returns an error if the `VERSION_CODENAME` from `/etc/os-release` is not `STABLE_SUITE`.
>> +///
>> +/// Also handles the special case `buster/updates` -> `bullseye-security` when the URI is
>> +/// security.debian.org, but fails if there's additional URIs.
>> +pub fn release_upgrade(files: &mut [APTRepositoryFile]) -> Result<(), Error> {
>> + let upgrade_suite = match UPGRADE_SUITE {
>> + Some(suite) => suite,
>> + None => bail!("release upgrade is not yet possible"),
>> + };
>> +
>> + let current = get_release_codename()?;
>> +
>> + if current == upgrade_suite {
>> + bail!("already installed '{}'", current);
>> + }
>> +
>> + if current != STABLE_SUITE {
>> + bail!(
>> + "unexpected release '{}' - cannot prepare repositories for upgrade",
>> + current
>> + );
>> + }
>> +
>> + for file in files.iter_mut() {
>> + for repo in file.repositories.iter_mut() {
>> + if !repo.enabled {
>> + continue;
>> + }
>> +
>> + for i in 0..repo.suites.len() {
>> + let suite = &repo.suites[i];
>> +
>> + // FIXME special case for security repository can be removed for Debian Bookworm
>> +
>> + let is_security_uri = |uri| {
>> + check::host_from_uri(uri).map_or(false, |host| host == "security.debian.org")
>
> this should probably also check for the not uncommon case of
>
> https://deb.debian.org/debian-security (or http)
>
Thanks for the info. The Debian wiki[0] only mentions
security.debian.org, so I wasn't aware of that. And somebody might have
a FQDN here too...
[0]: https://wiki.debian.org/NewInBullseye
>> + };
>> +
>> + let has_security_uri = repo.uris.iter().any(|uri| is_security_uri(uri));
>> + let has_only_security_uri = repo.uris.iter().all(|uri| is_security_uri(uri));
>> +
>> + if suite == "buster/updates" && has_security_uri {
>> + if !has_only_security_uri {
>> + bail!("cannot replace 'buster/updates' suite - multiple URIs");
>> + }
>> +
>> + repo.suites[i] = "bullseye-security".to_string();
>> +
>> + continue;
>> + }
>> +
>> + let (base, variant) = check::suite_variant(suite);
>> + if base == STABLE_SUITE {
>> + repo.suites[i] = format!("{}{}", upgrade_suite, variant);
>> + }
>> + }
>> + }
>> + }
>> +
>> + Ok(())
>> +}
>> +
>> /// Write the repositories for each file.
>> ///
>> /// Returns an error for each file that could not be written successfully.
>> diff --git a/tests/repositories.rs b/tests/repositories.rs
>> index 3919077..ee7f1a8 100644
>> --- a/tests/repositories.rs
>> +++ b/tests/repositories.rs
>> @@ -4,7 +4,7 @@ use anyhow::{bail, format_err, Error};
>>
>> use proxmox_apt::repositories::{
>> check_repositories, common_digest, enterprise_repository_enabled,
>> - no_subscription_repository_enabled, write_repositories,
>> + no_subscription_repository_enabled, release_upgrade, write_repositories,
>> };
>> use proxmox_apt::types::{APTRepositoryFile, APTRepositoryInfo};
>>
>> @@ -292,3 +292,80 @@ fn test_common_digest() -> Result<(), Error> {
>>
>> Ok(())
>> }
>> +
>> +#[test]
>> +fn test_release_upgrade() -> Result<(), Error> {
>> + let test_dir = std::env::current_dir()?.join("tests");
>> + let read_dir = test_dir.join("sources.list.d");
>> + let write_dir = test_dir.join("sources.list.d.upgraded.actual");
>> + let expected_dir = test_dir.join("sources.list.d.upgraded.expected");
>> +
>> + if write_dir.is_dir() {
>> + std::fs::remove_dir_all(&write_dir)
>> + .map_err(|err| format_err!("unable to remove dir {:?} - {}", write_dir, err))?;
>> + }
>> +
>> + std::fs::create_dir_all(&write_dir)
>> + .map_err(|err| format_err!("unable to create dir {:?} - {}", write_dir, err))?;
>> +
>> + let mut files = vec![];
>> + let mut errors = vec![];
>> +
>> + for entry in std::fs::read_dir(read_dir)? {
>> + let path = entry?.path();
>> +
>> + match APTRepositoryFile::new(&path)? {
>> + Some(mut file) => match file.parse() {
>> + Ok(()) => files.push(file),
>> + Err(err) => errors.push(err),
>> + },
>> + None => bail!("unexpected None for '{:?}'", path),
>> + }
>> + }
>> +
>> + assert!(errors.is_empty());
>> +
>> + for file in files.iter_mut() {
>> + let path = PathBuf::from(&file.path);
>> + let new_path = write_dir.join(path.file_name().unwrap());
>> + file.path = new_path.into_os_string().into_string().unwrap();
>> + file.digest = None;
>> + }
>> +
>> + let res = release_upgrade(&mut files);
>> +
>> + // FIXME adapt test after branching off the stable-X branch!
>> + assert!(res.is_err());
>> + if res.is_err() {
>> + return Ok(());
>> + }
>> +
>> + write_repositories(&files).map_err(|err| format_err!("{:?}", err))?;
>> +
>> + let mut expected_count = 0;
>> +
>> + for entry in std::fs::read_dir(expected_dir)? {
>> + expected_count += 1;
>> +
>> + let expected_path = entry?.path();
>> + let actual_path = write_dir.join(expected_path.file_name().unwrap());
>> +
>> + let expected_contents = std::fs::read(&expected_path)
>> + .map_err(|err| format_err!("unable to read {:?} - {}", expected_path, err))?;
>> +
>> + let actual_contents = std::fs::read(&actual_path)
>> + .map_err(|err| format_err!("unable to read {:?} - {}", actual_path, err))?;
>> +
>> + assert_eq!(
>> + expected_contents, actual_contents,
>> + "Use\n\ndiff {:?} {:?}\n\nif you're not fluent in byte decimals",
>> + expected_path, actual_path
>> + );
>> + }
>> +
>> + let actual_count = std::fs::read_dir(write_dir)?.count();
>> +
>> + assert_eq!(expected_count, actual_count);
>> +
>> + Ok(())
>> +}
>> --
>> 2.20.1
>>
>>
>>
>> _______________________________________________
>> pbs-devel mailing list
>> pbs-devel at lists.proxmox.com
>> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
>>
>>
>>
>
>
> _______________________________________________
> pbs-devel mailing list
> pbs-devel at lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
>
>
More information about the pve-devel
mailing list