[pve-devel] [PATCH v7 proxmox-apt 4/5] add handling of Proxmox standard repositories

Fabian Ebner f.ebner at proxmox.com
Wed Jun 23 15:38:57 CEST 2021


Get handles for the available repositories along with their current
configuration status and make it possible to add them.

Signed-off-by: Fabian Ebner <f.ebner at proxmox.com>
---

New in v7.

This also generalizes the Proxmox repository detection logic that was present in
v6 and replaces the is_enterprise_enabled and is_nosubscription_enabled
functions.

 src/repositories/mod.rs        |  83 +++++++++++++++
 src/repositories/repository.rs |  14 +++
 src/repositories/standard.rs   | 180 +++++++++++++++++++++++++++++++++
 tests/repositories.rs          |  82 ++++++++++++++-
 4 files changed, 358 insertions(+), 1 deletion(-)
 create mode 100644 src/repositories/standard.rs

diff --git a/src/repositories/mod.rs b/src/repositories/mod.rs
index fc54857..eba65f4 100644
--- a/src/repositories/mod.rs
+++ b/src/repositories/mod.rs
@@ -12,6 +12,10 @@ mod file;
 pub use file::{APTRepositoryFile, APTRepositoryFileError, APTRepositoryInfo};
 
 mod release;
+use release::get_current_release_codename;
+
+mod standard;
+pub use standard::{APTRepositoryHandle, APTStandardRepository};
 
 const APT_SOURCES_LIST_FILENAME: &str = "/etc/apt/sources.list";
 const APT_SOURCES_LIST_DIRECTORY: &str = "/etc/apt/sources.list.d/";
@@ -56,6 +60,85 @@ pub fn check_repositories(files: &[APTRepositoryFile]) -> Result<Vec<APTReposito
     Ok(infos)
 }
 
+/// Get the repository associated to the handle and the path where its usually configured.
+pub fn get_standard_repository(
+    handle: APTRepositoryHandle,
+    product: &str,
+) -> Result<(APTRepository, String), Error> {
+    let suite = get_current_release_codename()?;
+
+    let repo = handle.to_repository(product, &suite);
+    let path = handle.path(product);
+
+    Ok((repo, path))
+}
+
+/// Return handles for standard Proxmox repositories and whether their status, where
+/// None means not configured, and Some(bool) indicates enabled or disabled
+pub fn standard_repositories(
+    product: &str,
+    files: &[APTRepositoryFile],
+) -> Vec<APTStandardRepository> {
+    let mut result = vec![
+        APTStandardRepository {
+            handle: APTRepositoryHandle::Enterprise,
+            status: None,
+            name: APTRepositoryHandle::Enterprise.name(product),
+        },
+        APTStandardRepository {
+            handle: APTRepositoryHandle::NoSubscription,
+            status: None,
+            name: APTRepositoryHandle::NoSubscription.name(product),
+        },
+        APTStandardRepository {
+            handle: APTRepositoryHandle::Test,
+            status: None,
+            name: APTRepositoryHandle::Test.name(product),
+        },
+    ];
+
+    if product == "pve" {
+        result.append(&mut vec![
+            APTStandardRepository {
+                handle: APTRepositoryHandle::CephPacific,
+                status: None,
+                name: APTRepositoryHandle::CephPacific.name(product),
+            },
+            APTStandardRepository {
+                handle: APTRepositoryHandle::CephPacificTest,
+                status: None,
+                name: APTRepositoryHandle::CephPacificTest.name(product),
+            },
+            APTStandardRepository {
+                handle: APTRepositoryHandle::CephOctopus,
+                status: None,
+                name: APTRepositoryHandle::CephOctopus.name(product),
+            },
+            APTStandardRepository {
+                handle: APTRepositoryHandle::CephOctopusTest,
+                status: None,
+                name: APTRepositoryHandle::CephOctopusTest.name(product),
+            },
+        ]);
+    }
+
+    for file in files.iter() {
+        for repo in file.repositories.iter() {
+            for entry in result.iter_mut() {
+                if entry.status == Some(true) {
+                    continue;
+                }
+
+                if repo.is_referenced_repository(entry.handle, product) {
+                    entry.status = Some(repo.enabled);
+                }
+            }
+        }
+    }
+
+    result
+}
+
 /// Returns all APT repositories configured in `/etc/apt/sources.list` and
 /// in `/etc/apt/sources.list.d` including disabled repositories.
 ///
diff --git a/src/repositories/repository.rs b/src/repositories/repository.rs
index 875e4ee..d0a2b81 100644
--- a/src/repositories/repository.rs
+++ b/src/repositories/repository.rs
@@ -7,6 +7,8 @@ use serde::{Deserialize, Serialize};
 
 use proxmox::api::api;
 
+use crate::repositories::standard::APTRepositoryHandle;
+
 #[api]
 #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(rename_all = "lowercase")]
@@ -266,6 +268,18 @@ impl APTRepository {
         Ok(())
     }
 
+    /// Checks if the repository is the one referenced by the handle.
+    pub fn is_referenced_repository(&self, handle: APTRepositoryHandle, product: &str) -> bool {
+        let (package_type, uri, component) = handle.info(product);
+
+        self.types.contains(&package_type)
+            && self
+                .uris
+                .iter()
+                .any(|self_uri| self_uri.trim_end_matches('/') == uri)
+            && self.components.contains(&component)
+    }
+
     /// Check if a variant of the given suite is configured in this repository
     pub fn has_suite_variant(&self, base_suite: &str) -> bool {
         self.suites
diff --git a/src/repositories/standard.rs b/src/repositories/standard.rs
new file mode 100644
index 0000000..2a29852
--- /dev/null
+++ b/src/repositories/standard.rs
@@ -0,0 +1,180 @@
+use std::convert::TryFrom;
+use std::fmt::Display;
+
+use anyhow::{bail, Error};
+use serde::{Deserialize, Serialize};
+
+use crate::repositories::repository::{
+    APTRepository, APTRepositoryFileType, APTRepositoryPackageType,
+};
+
+use proxmox::api::api;
+
+#[api(
+    properties: {
+        handle: {
+            description: "Handle referencing a standard repository.",
+            type: String,
+        },
+    },
+)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "kebab-case")]
+/// Reference to a standard repository and configuration status.
+pub struct APTStandardRepository {
+    /// Handle referencing a standard repository.
+    pub handle: APTRepositoryHandle,
+
+    /// Configuration status of the associated repository.
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub status: Option<bool>,
+
+    /// Full name of the repository.
+    pub name: String,
+}
+
+#[api]
+#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "kebab-case")]
+/// Handles for Proxmox repositories.
+pub enum APTRepositoryHandle {
+    /// The enterprise repository for production use.
+    Enterprise,
+    /// The repository that can be used without subscription.
+    NoSubscription,
+    /// The test repository.
+    Test,
+    /// Ceph Pacific repository.
+    CephPacific,
+    /// Ceph Pacific test repository.
+    CephPacificTest,
+    /// Ceph Octoput repository.
+    CephOctopus,
+    /// Ceph Octoput test repository.
+    CephOctopusTest,
+}
+
+impl TryFrom<&str> for APTRepositoryHandle {
+    type Error = Error;
+
+    fn try_from(string: &str) -> Result<Self, Error> {
+        match string {
+            "enterprise" => Ok(APTRepositoryHandle::Enterprise),
+            "no-subscription" => Ok(APTRepositoryHandle::NoSubscription),
+            "test" => Ok(APTRepositoryHandle::Test),
+            "ceph-pacific" => Ok(APTRepositoryHandle::CephPacific),
+            "ceph-pacific-test" => Ok(APTRepositoryHandle::CephPacificTest),
+            "ceph-octopus" => Ok(APTRepositoryHandle::CephOctopus),
+            "ceph-octopus-test" => Ok(APTRepositoryHandle::CephOctopusTest),
+            _ => bail!("unknown repository handle '{}'", string),
+        }
+    }
+}
+
+impl Display for APTRepositoryHandle {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            APTRepositoryHandle::Enterprise => write!(f, "enterprise"),
+            APTRepositoryHandle::NoSubscription => write!(f, "no-subscription"),
+            APTRepositoryHandle::Test => write!(f, "test"),
+            APTRepositoryHandle::CephPacific => write!(f, "ceph-pacific"),
+            APTRepositoryHandle::CephPacificTest => write!(f, "ceph-pacific-test"),
+            APTRepositoryHandle::CephOctopus => write!(f, "ceph-octopus"),
+            APTRepositoryHandle::CephOctopusTest => write!(f, "ceph-octopus-test"),
+        }
+    }
+}
+
+impl APTRepositoryHandle {
+    /// Get the full name of the repository.
+    pub fn name(self, product: &str) -> String {
+        match self {
+            APTRepositoryHandle::Enterprise => {
+                format!("{} Enterprise Repository", product.to_uppercase())
+            }
+            APTRepositoryHandle::NoSubscription => {
+                format!("{} No-Subscription Repository", product.to_uppercase())
+            }
+            APTRepositoryHandle::Test => format!("{} Test Repository", product.to_uppercase()),
+            APTRepositoryHandle::CephPacific => "PVE Ceph Pacific Repository".to_string(),
+            APTRepositoryHandle::CephPacificTest => "PVE Ceph Pacific Test Repository".to_string(),
+            APTRepositoryHandle::CephOctopus => "PVE Ceph Octopus Repository".to_string(),
+            APTRepositoryHandle::CephOctopusTest => "PVE Ceph Octopus Test Repository".to_string(),
+        }
+    }
+
+    /// Get the standard file path for the repository referenced by the handle.
+    pub fn path(self, product: &str) -> String {
+        match self {
+            APTRepositoryHandle::Enterprise => {
+                format!("/etc/apt/sources.list.d/{}-enterprise.list", product)
+            }
+            APTRepositoryHandle::NoSubscription => "/etc/apt/sources.list".to_string(),
+            APTRepositoryHandle::Test => "/etc/apt/sources.list".to_string(),
+            APTRepositoryHandle::CephPacific => "/etc/apt/sources.list.d/ceph.list".to_string(),
+            APTRepositoryHandle::CephPacificTest => "/etc/apt/sources.list.d/ceph.list".to_string(),
+            APTRepositoryHandle::CephOctopus => "/etc/apt/sources.list.d/ceph.list".to_string(),
+            APTRepositoryHandle::CephOctopusTest => "/etc/apt/sources.list.d/ceph.list".to_string(),
+        }
+    }
+
+    /// Get package type, URI and the component associated with the handle.
+    pub fn info(self, product: &str) -> (APTRepositoryPackageType, String, String) {
+        match self {
+            APTRepositoryHandle::Enterprise => (
+                APTRepositoryPackageType::Deb,
+                format!("https://enterprise.proxmox.com/debian/{}", product),
+                format!("{}-enterprise", product),
+            ),
+            APTRepositoryHandle::NoSubscription => (
+                APTRepositoryPackageType::Deb,
+                format!("http://download.proxmox.com/debian/{}", product),
+                format!("{}-no-subscription", product),
+            ),
+            APTRepositoryHandle::Test => (
+                APTRepositoryPackageType::Deb,
+                format!("http://download.proxmox.com/debian/{}", product),
+                format!("{}test", product),
+            ),
+            APTRepositoryHandle::CephPacific => (
+                APTRepositoryPackageType::Deb,
+                "http://download.proxmox.com/debian/ceph-pacific".to_string(),
+                "main".to_string(),
+            ),
+            APTRepositoryHandle::CephPacificTest => (
+                APTRepositoryPackageType::Deb,
+                "http://download.proxmox.com/debian/ceph-pacific".to_string(),
+                "test".to_string(),
+            ),
+            APTRepositoryHandle::CephOctopus => (
+                APTRepositoryPackageType::Deb,
+                "http://download.proxmox.com/debian/ceph-octopus".to_string(),
+                "main".to_string(),
+            ),
+            APTRepositoryHandle::CephOctopusTest => (
+                APTRepositoryPackageType::Deb,
+                "http://download.proxmox.com/debian/ceph-octopus".to_string(),
+                "test".to_string(),
+            ),
+        }
+    }
+
+    /// Get the standard repository referenced by the handle.
+    ///
+    /// An URI in the result is not '/'-terminated (under the assumption that no valid
+    /// product name is).
+    pub fn to_repository(self, product: &str, suite: &str) -> APTRepository {
+        let (package_type, uri, component) = self.info(product);
+
+        APTRepository {
+            types: vec![package_type],
+            uris: vec![uri],
+            suites: vec![suite.to_string()],
+            components: vec![component],
+            options: vec![],
+            comment: String::new(),
+            file_type: APTRepositoryFileType::List,
+            enabled: true,
+        }
+    }
+}
diff --git a/tests/repositories.rs b/tests/repositories.rs
index 58f1322..d0e9329 100644
--- a/tests/repositories.rs
+++ b/tests/repositories.rs
@@ -2,7 +2,10 @@ use std::path::PathBuf;
 
 use anyhow::{bail, format_err, Error};
 
-use proxmox_apt::repositories::{check_repositories, APTRepositoryFile, APTRepositoryInfo};
+use proxmox_apt::repositories::{
+    check_repositories, standard_repositories, APTRepositoryFile, APTRepositoryHandle,
+    APTRepositoryInfo, APTStandardRepository,
+};
 
 #[test]
 fn test_parse_write() -> Result<(), Error> {
@@ -264,3 +267,80 @@ fn test_check_repositories() -> Result<(), Error> {
 
     Ok(())
 }
+#[test]
+fn test_standard_repositories() -> Result<(), Error> {
+    let test_dir = std::env::current_dir()?.join("tests");
+    let read_dir = test_dir.join("sources.list.d");
+
+    let mut expected = vec![
+        APTStandardRepository {
+            handle: APTRepositoryHandle::Enterprise,
+            status: None,
+            name: APTRepositoryHandle::Enterprise.name("pve"),
+        },
+        APTStandardRepository {
+            handle: APTRepositoryHandle::NoSubscription,
+            status: None,
+            name: APTRepositoryHandle::NoSubscription.name("pve"),
+        },
+        APTStandardRepository {
+            handle: APTRepositoryHandle::Test,
+            status: None,
+            name: APTRepositoryHandle::Test.name("pve"),
+        },
+        APTStandardRepository {
+            handle: APTRepositoryHandle::CephPacific,
+            status: None,
+            name: APTRepositoryHandle::CephPacific.name("pve"),
+        },
+        APTStandardRepository {
+            handle: APTRepositoryHandle::CephPacificTest,
+            status: None,
+            name: APTRepositoryHandle::CephPacificTest.name("pve"),
+        },
+        APTStandardRepository {
+            handle: APTRepositoryHandle::CephOctopus,
+            status: None,
+            name: APTRepositoryHandle::CephOctopus.name("pve"),
+        },
+        APTStandardRepository {
+            handle: APTRepositoryHandle::CephOctopusTest,
+            status: None,
+            name: APTRepositoryHandle::CephOctopusTest.name("pve"),
+        },
+    ];
+
+    let absolute_suite_list = read_dir.join("absolute_suite.list");
+    let mut file = APTRepositoryFile::new(&absolute_suite_list)?.unwrap();
+    file.parse()?;
+
+    let std_repos = standard_repositories("pve", &vec![file]);
+
+    assert_eq!(std_repos, expected);
+
+    let pve_list = read_dir.join("pve.list");
+    let mut file = APTRepositoryFile::new(&pve_list)?.unwrap();
+    file.parse()?;
+
+    let file_vec = vec![file];
+
+    let std_repos = standard_repositories("pbs", &file_vec);
+
+    expected[0].name = APTRepositoryHandle::Enterprise.name("pbs");
+    expected[1].name = APTRepositoryHandle::NoSubscription.name("pbs");
+    expected[2].name = APTRepositoryHandle::Test.name("pbs");
+
+    assert_eq!(&std_repos, &expected[0..=2]);
+
+    expected[0].status = Some(false);
+    expected[1].status = Some(true);
+    expected[0].name = APTRepositoryHandle::Enterprise.name("pve");
+    expected[1].name = APTRepositoryHandle::NoSubscription.name("pve");
+    expected[2].name = APTRepositoryHandle::Test.name("pve");
+
+    let std_repos = standard_repositories("pve", &file_vec);
+
+    assert_eq!(std_repos, expected);
+
+    Ok(())
+}
-- 
2.30.2






More information about the pve-devel mailing list