[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