From: Fabian Ebner <f.ebner@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH v7 proxmox-apt 4/5] add handling of Proxmox standard repositories
Date: Wed, 23 Jun 2021 15:38:57 +0200 [thread overview]
Message-ID: <20210623133904.174072-5-f.ebner@proxmox.com> (raw)
In-Reply-To: <20210623133904.174072-1-f.ebner@proxmox.com>
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@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
next prev parent reply other threads:[~2021-06-23 13:39 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-06-23 13:38 [pve-devel] [PATCH-SERIES v7] APT repositories API/UI Fabian Ebner
2021-06-23 13:38 ` [pve-devel] [PATCH v7 proxmox-apt 1/5] initial commit Fabian Ebner
2021-06-23 13:38 ` [pve-devel] [PATCH v7 proxmox-apt 2/5] add files for Debian packaging Fabian Ebner
2021-06-23 13:38 ` [pve-devel] [PATCH v7 proxmox-apt 3/5] add more functions to check repositories Fabian Ebner
2021-06-23 13:38 ` Fabian Ebner [this message]
2021-06-23 13:38 ` [pve-devel] [PATCH v7 proxmox-apt 5/5] bump version to 0.2.0-1 Fabian Ebner
2021-06-23 13:38 ` [pve-devel] [PATCH v7 proxmox-widget-toolkit 1/2] add UI for APT repositories Fabian Ebner
2021-06-23 13:39 ` [pve-devel] [PATCH v7 proxmox-widget-toolkit 2/2] add buttons for add/enable/disable Fabian Ebner
2021-06-23 13:39 ` [pve-devel] [PATCH v7 pve-rs 1/1] add bindings for proxmox-apt Fabian Ebner
2021-06-30 19:17 ` [pve-devel] applied: " Thomas Lamprecht
2021-06-23 13:39 ` [pve-devel] [PATCH v7 pve-manager 1/3] api: apt: add call for repository information Fabian Ebner
2021-06-23 13:39 ` [pve-devel] [PATCH v7 pve-manager 2/3] api: apt: add PUT and POST handler for repositories Fabian Ebner
2021-06-23 13:39 ` [pve-devel] [PATCH v7 pve-manager 3/3] ui: add panel for listing APT repositories Fabian Ebner
2021-06-23 18:02 ` [pve-devel] partially-applied: [PATCH-SERIES v7] APT repositories API/UI Thomas Lamprecht
2021-07-05 6:51 ` [pve-devel] applied-series: " Thomas Lamprecht
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210623133904.174072-5-f.ebner@proxmox.com \
--to=f.ebner@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal