[pve-devel] [pbs-devel] [PATCH v6 proxmox-apt 06/11] add release_upgrade function and constants for the current and upgrade suite

Fabian Grünbichler f.gruenbichler at proxmox.com
Thu Jun 17 16:16:34 CEST 2021


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)

> +                };
> +
> +                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
> 
> 
> 





More information about the pve-devel mailing list