mirror of
https://git.proxmox.com/git/proxmox
synced 2025-08-06 16:48:10 +00:00
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:
parent
76d3a5ba1f
commit
8ada17854d
@ -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.
|
||||
///
|
||||
|
@ -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
|
||||
|
180
src/repositories/standard.rs
Normal file
180
src/repositories/standard.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
@ -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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user