mirror of
https://git.proxmox.com/git/proxmox
synced 2025-08-07 21:18:51 +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};
|
pub use file::{APTRepositoryFile, APTRepositoryFileError, APTRepositoryInfo};
|
||||||
|
|
||||||
mod release;
|
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_FILENAME: &str = "/etc/apt/sources.list";
|
||||||
const APT_SOURCES_LIST_DIRECTORY: &str = "/etc/apt/sources.list.d/";
|
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)
|
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
|
/// Returns all APT repositories configured in `/etc/apt/sources.list` and
|
||||||
/// in `/etc/apt/sources.list.d` including disabled repositories.
|
/// in `/etc/apt/sources.list.d` including disabled repositories.
|
||||||
///
|
///
|
||||||
|
@ -7,6 +7,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use proxmox::api::api;
|
use proxmox::api::api;
|
||||||
|
|
||||||
|
use crate::repositories::standard::APTRepositoryHandle;
|
||||||
|
|
||||||
#[api]
|
#[api]
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
@ -266,6 +268,18 @@ impl APTRepository {
|
|||||||
Ok(())
|
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
|
/// Check if a variant of the given suite is configured in this repository
|
||||||
pub fn has_suite_variant(&self, base_suite: &str) -> bool {
|
pub fn has_suite_variant(&self, base_suite: &str) -> bool {
|
||||||
self.suites
|
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 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]
|
#[test]
|
||||||
fn test_parse_write() -> Result<(), Error> {
|
fn test_parse_write() -> Result<(), Error> {
|
||||||
@ -264,3 +267,80 @@ fn test_check_repositories() -> Result<(), Error> {
|
|||||||
|
|
||||||
Ok(())
|
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