add handling of Proxmox standard repositories

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>
This commit is contained in:
Fabian Ebner 2021-06-23 15:38:57 +02:00 committed by Thomas Lamprecht
parent 76d3a5ba1f
commit 8ada17854d
4 changed files with 358 additions and 1 deletions

View File

@ -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.
///

View File

@ -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

View File

@ -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,
}
}
}

View File

@ -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(())
}