From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 44AF17517A for ; Wed, 23 Jun 2021 15:39:19 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 5B0A4B753 for ; Wed, 23 Jun 2021 15:39:17 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id C0F62B63D for ; Wed, 23 Jun 2021 15:39:10 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 954CD46779 for ; Wed, 23 Jun 2021 15:39:10 +0200 (CEST) From: Fabian Ebner To: pve-devel@lists.proxmox.com Date: Wed, 23 Jun 2021 15:38:57 +0200 Message-Id: <20210623133904.174072-5-f.ebner@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210623133904.174072-1-f.ebner@proxmox.com> References: <20210623133904.174072-1-f.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.676 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pve-devel] [PATCH v7 proxmox-apt 4/5] add handling of Proxmox standard repositories X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 23 Jun 2021 13:39:19 -0000 Get handles for the available repositories along with their current configuration status and make it possible to add them. Signed-off-by: Fabian Ebner --- 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 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 { + 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, + + /// 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 { + 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