use new apt/apt-api-types crate

This commit is contained in:
Dietmar Maurer 2024-06-21 11:51:30 +02:00 committed by Wolfgang Bumiller
parent da2002eadb
commit 55e7bef4d2
8 changed files with 72 additions and 694 deletions

View File

@ -53,7 +53,9 @@ path = "src/lib.rs"
[workspace.dependencies]
# proxmox workspace
proxmox-apt = "0.10.5"
proxmox-config-digest = "0.1.0"
proxmox-apt = { version = "0.11", features = [ "cache" ] }
proxmox-apt-api-types = "1.0"
proxmox-async = "0.4"
proxmox-auth-api = "0.4"
proxmox-borrow = "1"
@ -203,9 +205,11 @@ zstd.workspace = true
# proxmox workspace
proxmox-apt.workspace = true
proxmox-apt-api-types.workspace = true
proxmox-async.workspace = true
proxmox-auth-api = { workspace = true, features = [ "api", "pam-authenticator" ] }
proxmox-compression.workspace = true
proxmox-config-digest.workspace = true
proxmox-http = { workspace = true, features = [ "client-trait", "proxmox-async", "rate-limited-stream" ] } # pbs-client doesn't use these
proxmox-human-byte.workspace = true
proxmox-io.workspace = true

View File

@ -16,6 +16,7 @@ serde.workspace = true
serde_plain.workspace = true
proxmox-auth-api = { workspace = true, features = [ "api-types" ] }
proxmox-apt-api-types.workspace = true
proxmox-human-byte.workspace = true
proxmox-lang.workspace=true
proxmox-schema = { workspace = true, features = [ "api-macro" ] }

View File

@ -52,6 +52,13 @@ pub use proxmox_schema::api_types::{SYSTEMD_DATETIME_FORMAT, TIME_ZONE_SCHEMA};
use proxmox_schema::api_types::{DNS_NAME_STR, IPRE_BRACKET_STR};
// re-export APT API types
pub use proxmox_apt_api_types::{
APTChangeRepositoryOptions, APTGetChangelogOptions, APTRepositoriesResult, APTRepositoryFile,
APTRepositoryFileError, APTRepositoryHandle, APTRepositoryInfo, APTStandardRepository,
APTUpdateInfo, APTUpdateOptions,
};
#[rustfmt::skip]
pub const BACKUP_ID_RE: &str = r"[A-Za-z0-9_][A-Za-z0-9._\-]*";
@ -249,34 +256,6 @@ pub const PASSWORD_HINT_SCHEMA: Schema = StringSchema::new("Password hint.")
.max_length(64)
.schema();
#[api()]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
/// Describes a package for which an update is available.
pub struct APTUpdateInfo {
/// Package name
pub package: String,
/// Package title
pub title: String,
/// Package architecture
pub arch: String,
/// Human readable package description
pub description: String,
/// New version to be updated to
pub version: String,
/// Old version currently installed
pub old_version: String,
/// Package origin
pub origin: String,
/// Package priority in human-readable form
pub priority: String,
/// Package section
pub section: String,
/// Custom extra field for additional package information
#[serde(skip_serializing_if = "Option::is_none")]
pub extra_info: Option<String>,
}
#[api()]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]

View File

@ -98,6 +98,8 @@ pub const PROXMOX_BACKUP_KERNEL_FN: &str =
pub const PROXMOX_BACKUP_SUBSCRIPTION_FN: &str = configdir!("/subscription");
pub const APT_PKG_STATE_FN: &str = concat!(PROXMOX_BACKUP_STATE_DIR_M!(), "/pkg-state.json");
/// Prepend configuration directory to a file name
///
/// This is a simply way to get the full path for configuration files.

View File

@ -1,26 +1,21 @@
use anyhow::{bail, format_err, Error};
use serde_json::{json, Value};
use std::os::unix::prelude::OsStrExt;
use anyhow::{bail, Error};
use proxmox_config_digest::ConfigDigest;
use proxmox_router::{
list_subdirs_api_method, Permission, Router, RpcEnvironment, RpcEnvironmentType, SubdirMap,
};
use proxmox_schema::api;
use proxmox_sys::fs::{replace_file, CreateOptions};
use proxmox_apt::repositories::{
APTRepositoryFile, APTRepositoryFileError, APTRepositoryHandle, APTRepositoryInfo,
APTStandardRepository,
use proxmox_apt_api_types::{
APTChangeRepositoryOptions, APTGetChangelogOptions, APTRepositoriesResult, APTRepositoryHandle,
APTUpdateInfo, APTUpdateOptions,
};
use proxmox_http::ProxyConfig;
use pbs_api_types::{
APTUpdateInfo, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA,
UPID_SCHEMA,
};
use pbs_api_types::{NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, UPID_SCHEMA};
use crate::config::node;
use crate::tools::apt;
use proxmox_rest_server::WorkerTask;
#[api(
@ -44,16 +39,8 @@ use proxmox_rest_server::WorkerTask;
},
)]
/// List available APT updates
fn apt_update_available(_param: Value) -> Result<Value, Error> {
if let Ok(false) = apt::pkg_cache_expired() {
if let Ok(Some(cache)) = apt::read_pkg_state() {
return Ok(json!(cache.package_status));
}
}
let cache = apt::update_cache()?;
Ok(json!(cache.package_status))
pub fn apt_update_available() -> Result<Vec<APTUpdateInfo>, Error> {
proxmox_apt::list_available_apt_update(pbs_buildcfg::APT_PKG_STATE_FN)
}
pub fn update_apt_proxy_config(proxy_config: Option<&ProxyConfig>) -> Result<(), Error> {
@ -83,45 +70,6 @@ fn read_and_update_proxy_config() -> Result<Option<ProxyConfig>, Error> {
Ok(proxy_config)
}
fn do_apt_update(worker: &WorkerTask, quiet: bool) -> Result<(), Error> {
if !quiet {
worker.log_message("starting apt-get update")
}
read_and_update_proxy_config()?;
let mut command = std::process::Command::new("apt-get");
command.arg("update");
// apt "errors" quite easily, and run_command is a bit rigid, so handle this inline for now.
let output = command
.output()
.map_err(|err| format_err!("failed to execute {:?} - {}", command, err))?;
if !quiet {
worker.log_message(String::from_utf8(output.stdout)?);
}
// TODO: improve run_command to allow outputting both, stderr and stdout
if !output.status.success() {
if output.status.code().is_some() {
let msg = String::from_utf8(output.stderr)
.map(|m| {
if m.is_empty() {
String::from("no error message")
} else {
m
}
})
.unwrap_or_else(|_| String::from("non utf8 error message (suppressed)"));
worker.log_warning(msg);
} else {
bail!("terminated by signal");
}
}
Ok(())
}
#[api(
protected: true,
input: {
@ -129,19 +77,10 @@ fn do_apt_update(worker: &WorkerTask, quiet: bool) -> Result<(), Error> {
node: {
schema: NODE_SCHEMA,
},
notify: {
type: bool,
description: r#"Send notification mail about new package updates available to the
email address configured for 'root@pam')."#,
default: false,
optional: true,
},
quiet: {
description: "Only produces output suitable for logging, omitting progress indicators.",
type: bool,
default: false,
optional: true,
},
options: {
type: APTUpdateOptions,
flatten: true,
}
},
},
returns: {
@ -153,40 +92,22 @@ fn do_apt_update(worker: &WorkerTask, quiet: bool) -> Result<(), Error> {
)]
/// Update the APT database
pub fn apt_update_database(
notify: bool,
quiet: bool,
options: APTUpdateOptions,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<String, Error> {
let auth_id = rpcenv.get_auth_id().unwrap();
let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI;
let upid_str = WorkerTask::new_thread("aptupdate", None, auth_id, to_stdout, move |worker| {
do_apt_update(&worker, quiet)?;
let mut cache = apt::update_cache()?;
if notify {
let mut notified = cache.notified.unwrap_or_default();
let mut to_notify: Vec<&APTUpdateInfo> = Vec::new();
for pkg in &cache.package_status {
match notified.insert(pkg.package.to_owned(), pkg.version.to_owned()) {
Some(notified_version) => {
if notified_version != pkg.version {
to_notify.push(pkg);
}
}
None => to_notify.push(pkg),
}
}
if !to_notify.is_empty() {
to_notify.sort_unstable_by_key(|k| &k.package);
crate::server::send_updates_available(&to_notify)?;
}
cache.notified = Some(notified);
apt::write_pkg_cache(&cache)?;
}
let upid_str = WorkerTask::new_thread("aptupdate", None, auth_id, to_stdout, move |_worker| {
read_and_update_proxy_config()?;
proxmox_apt::update_database(
pbs_buildcfg::APT_PKG_STATE_FN,
&options,
|updates: &[&APTUpdateInfo]| {
crate::server::send_updates_available(updates)?;
Ok(())
},
)?;
Ok(())
})?;
@ -200,14 +121,9 @@ pub fn apt_update_database(
node: {
schema: NODE_SCHEMA,
},
name: {
description: "Package name to get changelog of.",
type: String,
},
version: {
description: "Package version to get changelog of. Omit to use candidate version.",
type: String,
optional: true,
options: {
type: APTGetChangelogOptions,
flatten: true,
},
},
},
@ -219,17 +135,8 @@ pub fn apt_update_database(
},
)]
/// Retrieve the changelog of the specified package.
fn apt_get_changelog(name: String, version: Option<String>) -> Result<Value, Error> {
let mut command = std::process::Command::new("apt-get");
command.arg("changelog");
command.arg("-qq"); // don't display download progress
if let Some(ver) = version {
command.arg(format!("{name}={ver}"));
} else {
command.arg(name);
}
let output = proxmox_sys::command::run_command(command, None)?;
Ok(json!(output))
fn apt_get_changelog(options: APTGetChangelogOptions) -> Result<String, Error> {
proxmox_apt::get_changelog(&options)
}
#[api(
@ -256,10 +163,8 @@ pub fn get_versions() -> Result<Vec<APTUpdateInfo>, Error> {
const PACKAGES: &[&str] = &[
"ifupdown2",
"libjs-extjs",
"proxmox-backup",
"proxmox-backup-docs",
"proxmox-backup-client",
"proxmox-backup-server",
"proxmox-mail-forward",
"proxmox-mini-journalreader",
"proxmox-offline-mirror-helper",
@ -269,96 +174,16 @@ pub fn get_versions() -> Result<Vec<APTUpdateInfo>, Error> {
"zfsutils-linux",
];
fn unknown_package(package: String, extra_info: Option<String>) -> APTUpdateInfo {
APTUpdateInfo {
package,
title: "unknown".into(),
arch: "unknown".into(),
description: "unknown".into(),
version: "unknown".into(),
old_version: "unknown".into(),
origin: "unknown".into(),
priority: "unknown".into(),
section: "unknown".into(),
extra_info,
}
}
let is_kernel =
|name: &str| name.starts_with("pve-kernel-") || name.starts_with("proxmox-kernel");
let mut packages: Vec<APTUpdateInfo> = Vec::new();
let pbs_packages = apt::list_installed_apt_packages(
|filter| {
filter.installed_version == Some(filter.active_version)
&& (is_kernel(filter.package) || PACKAGES.contains(&filter.package))
},
None,
);
let running_kernel = format!(
"running kernel: {}",
std::str::from_utf8(nix::sys::utsname::uname()?.release().as_bytes())?.to_owned()
);
if let Some(proxmox_backup) = pbs_packages
.iter()
.find(|pkg| pkg.package == "proxmox-backup")
{
let mut proxmox_backup = proxmox_backup.clone();
proxmox_backup.extra_info = Some(running_kernel);
packages.push(proxmox_backup);
} else {
packages.push(unknown_package(
"proxmox-backup".into(),
Some(running_kernel),
));
}
let version = pbs_buildcfg::PROXMOX_PKG_VERSION;
let release = pbs_buildcfg::PROXMOX_PKG_RELEASE;
let daemon_version_info = Some(format!("running version: {}.{}", version, release));
if let Some(pkg) = pbs_packages
.iter()
.find(|pkg| pkg.package == "proxmox-backup-server")
{
let mut pkg = pkg.clone();
pkg.extra_info = daemon_version_info;
packages.push(pkg);
} else {
packages.push(unknown_package(
"proxmox-backup".into(),
daemon_version_info,
));
}
let running_daemon_version = format!("running version: {version}.{release}");
let mut kernel_pkgs: Vec<APTUpdateInfo> = pbs_packages
.iter()
.filter(|pkg| is_kernel(&pkg.package))
.cloned()
.collect();
// make sure the cache mutex gets dropped before the next call to list_installed_apt_packages
{
let cache = apt_pkg_native::Cache::get_singleton();
kernel_pkgs.sort_by(|left, right| {
cache
.compare_versions(&left.old_version, &right.old_version)
.reverse()
});
}
packages.append(&mut kernel_pkgs);
// add entry for all packages we're interested in, even if not installed
for pkg in PACKAGES.iter() {
if pkg == &"proxmox-backup" || pkg == &"proxmox-backup-server" {
continue;
}
match pbs_packages.iter().find(|item| &item.package == pkg) {
Some(apt_pkg) => packages.push(apt_pkg.to_owned()),
None => packages.push(unknown_package(pkg.to_string(), None)),
}
}
Ok(packages)
proxmox_apt::get_package_versions(
"proxmox-backup",
"proxmox-backup-server",
&running_daemon_version,
&PACKAGES,
)
}
#[api(
@ -370,61 +195,15 @@ pub fn get_versions() -> Result<Vec<APTUpdateInfo>, Error> {
},
},
returns: {
type: Object,
description: "Result from parsing the APT repository files in /etc/apt/.",
properties: {
files: {
description: "List of parsed repository files.",
type: Array,
items: {
type: APTRepositoryFile,
},
},
errors: {
description: "List of problematic files.",
type: Array,
items: {
type: APTRepositoryFileError,
},
},
digest: {
schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
},
infos: {
description: "List of additional information/warnings about the repositories.",
items: {
type: APTRepositoryInfo,
},
},
"standard-repos": {
description: "List of standard repositories and their configuration status.",
items: {
type: APTStandardRepository,
},
},
},
type: APTRepositoriesResult,
},
access: {
permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false),
},
)]
/// Get APT repository information.
pub fn get_repositories() -> Result<Value, Error> {
let (files, errors, digest) = proxmox_apt::repositories::repositories()?;
let digest = hex::encode(digest);
let suite = proxmox_apt::repositories::get_current_release_codename()?;
let infos = proxmox_apt::repositories::check_repositories(&files, suite);
let standard_repos = proxmox_apt::repositories::standard_repositories(&files, "pbs", suite);
Ok(json!({
"files": files,
"errors": errors,
"digest": digest,
"infos": infos,
"standard-repos": standard_repos,
}))
pub fn get_repositories() -> Result<APTRepositoriesResult, Error> {
proxmox_apt::list_repositories("pbs")
}
#[api(
@ -437,7 +216,7 @@ pub fn get_repositories() -> Result<Value, Error> {
type: APTRepositoryHandle,
},
digest: {
schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
type: ConfigDigest,
optional: true,
},
},
@ -451,61 +230,11 @@ pub fn get_repositories() -> Result<Value, Error> {
/// If the repository is already configured, it will be set to enabled.
///
/// The `digest` parameter asserts that the configuration has not been modified.
pub fn add_repository(handle: APTRepositoryHandle, digest: Option<String>) -> Result<(), Error> {
let (mut files, errors, current_digest) = proxmox_apt::repositories::repositories()?;
let suite = proxmox_apt::repositories::get_current_release_codename()?;
if let Some(expected_digest) = digest {
let current_digest = hex::encode(current_digest);
crate::tools::assert_if_modified(&expected_digest, &current_digest)?;
}
// check if it's already configured first
for file in files.iter_mut() {
for repo in file.repositories.iter_mut() {
if repo.is_referenced_repository(handle, "pbs", &suite.to_string()) {
if repo.enabled {
return Ok(());
}
repo.set_enabled(true);
file.write()?;
return Ok(());
}
}
}
let (repo, path) = proxmox_apt::repositories::get_standard_repository(handle, "pbs", suite);
if let Some(error) = errors.iter().find(|error| error.path == path) {
bail!(
"unable to parse existing file {} - {}",
error.path,
error.error,
);
}
if let Some(file) = files
.iter_mut()
.find(|file| file.path.as_ref() == Some(&path))
{
file.repositories.push(repo);
file.write()?;
} else {
let mut file = match APTRepositoryFile::new(&path)? {
Some(file) => file,
None => bail!("invalid path - {}", path),
};
file.repositories.push(repo);
file.write()?;
}
Ok(())
pub fn add_repository(
handle: APTRepositoryHandle,
digest: Option<ConfigDigest>,
) -> Result<(), Error> {
proxmox_apt::add_repository_handle("pbs", handle, digest)
}
#[api(
@ -522,13 +251,12 @@ pub fn add_repository(handle: APTRepositoryHandle, digest: Option<String>) -> Re
description: "Index within the file (starting from 0).",
type: usize,
},
enabled: {
description: "Whether the repository should be enabled or not.",
type: bool,
optional: true,
options: {
type: APTChangeRepositoryOptions,
flatten: true,
},
digest: {
schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
type: ConfigDigest,
optional: true,
},
},
@ -544,38 +272,10 @@ pub fn add_repository(handle: APTRepositoryHandle, digest: Option<String>) -> Re
pub fn change_repository(
path: String,
index: usize,
enabled: Option<bool>,
digest: Option<String>,
options: APTChangeRepositoryOptions,
digest: Option<ConfigDigest>,
) -> Result<(), Error> {
let (mut files, errors, current_digest) = proxmox_apt::repositories::repositories()?;
if let Some(expected_digest) = digest {
let current_digest = hex::encode(current_digest);
crate::tools::assert_if_modified(&expected_digest, &current_digest)?;
}
if let Some(error) = errors.iter().find(|error| error.path == path) {
bail!("unable to parse file {} - {}", error.path, error.error);
}
if let Some(file) = files
.iter_mut()
.find(|file| file.path.as_ref() == Some(&path))
{
if let Some(repo) = file.repositories.get_mut(index) {
if let Some(enabled) = enabled {
repo.set_enabled(enabled);
}
file.write()?;
} else {
bail!("invalid index - {}", index);
}
} else {
bail!("invalid path - {}", path);
}
Ok(())
proxmox_apt::change_repository(&path, index, &options, digest)
}
const SUBDIRS: SubdirMap = &[

View File

@ -5,7 +5,8 @@ use anyhow::{format_err, Error};
use regex::Regex;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use proxmox_apt::repositories::{self, APTRepositoryFile, APTRepositoryPackageType};
use proxmox_apt::repositories;
use proxmox_apt_api_types::{APTRepositoryFile, APTRepositoryPackageType};
use proxmox_backup::api2::node::apt;
const OLD_SUITE: &str = "bullseye";
@ -50,19 +51,18 @@ impl Checker {
fn check_upgradable_packages(&mut self) -> Result<(), Error> {
self.output.log_info("Checking for package updates..")?;
let result = Self::get_upgradable_packages();
let result = apt::apt_update_available();
match result {
Err(err) => {
self.output.log_warn(format!("{err}"))?;
self.output
.log_fail("unable to retrieve list of package updates!")?;
}
Ok(cache) => {
if cache.package_status.is_empty() {
Ok(package_status) => {
if package_status.is_empty() {
self.output.log_pass("all packages up-to-date")?;
} else {
let pkgs = cache
.package_status
let pkgs = package_status
.iter()
.map(|pkg| pkg.package.clone())
.collect::<Vec<String>>()
@ -452,20 +452,6 @@ impl Checker {
}
Ok(())
}
fn get_upgradable_packages() -> Result<proxmox_backup::tools::apt::PkgState, Error> {
let cache = if let Ok(false) = proxmox_backup::tools::apt::pkg_cache_expired() {
if let Ok(Some(cache)) = proxmox_backup::tools::apt::read_pkg_state() {
cache
} else {
proxmox_backup::tools::apt::update_cache()?
}
} else {
proxmox_backup::tools::apt::update_cache()?
};
Ok(cache)
}
}
#[derive(PartialEq)]

View File

@ -1,293 +0,0 @@
use std::collections::HashMap;
use std::collections::HashSet;
use anyhow::{bail, format_err, Error};
use apt_pkg_native::Cache;
use proxmox_schema::const_regex;
use proxmox_sys::fs::{file_read_optional_string, replace_file, CreateOptions};
use pbs_api_types::APTUpdateInfo;
use pbs_buildcfg::PROXMOX_BACKUP_STATE_DIR_M;
const APT_PKG_STATE_FN: &str = concat!(PROXMOX_BACKUP_STATE_DIR_M!(), "/pkg-state.json");
#[derive(Debug, serde::Serialize, serde::Deserialize)]
/// Some information we cache about the package (update) state, like what pending update version
/// we already notfied an user about
pub struct PkgState {
/// simple map from package name to most recently notified (emailed) version
pub notified: Option<HashMap<String, String>>,
/// A list of pending updates
pub package_status: Vec<APTUpdateInfo>,
}
pub fn write_pkg_cache(state: &PkgState) -> Result<(), Error> {
let serialized_state = serde_json::to_string(state)?;
replace_file(
APT_PKG_STATE_FN,
serialized_state.as_bytes(),
CreateOptions::new(),
false,
)
.map_err(|err| format_err!("Error writing package cache - {}", err))?;
Ok(())
}
pub fn read_pkg_state() -> Result<Option<PkgState>, Error> {
let serialized_state = match file_read_optional_string(APT_PKG_STATE_FN) {
Ok(Some(raw)) => raw,
Ok(None) => return Ok(None),
Err(err) => bail!("could not read cached package state file - {}", err),
};
serde_json::from_str(&serialized_state)
.map(Some)
.map_err(|err| format_err!("could not parse cached package status - {}", err))
}
pub fn pkg_cache_expired() -> Result<bool, Error> {
if let Ok(pbs_cache) = std::fs::metadata(APT_PKG_STATE_FN) {
let apt_pkgcache = std::fs::metadata("/var/cache/apt/pkgcache.bin")?;
let dpkg_status = std::fs::metadata("/var/lib/dpkg/status")?;
let mtime = pbs_cache.modified()?;
if apt_pkgcache.modified()? <= mtime && dpkg_status.modified()? <= mtime {
return Ok(false);
}
}
Ok(true)
}
pub fn update_cache() -> Result<PkgState, Error> {
// update our cache
let all_upgradeable = list_installed_apt_packages(
|data| {
data.candidate_version == data.active_version
&& data.installed_version != Some(data.candidate_version)
},
None,
);
let cache = match read_pkg_state() {
Ok(Some(mut cache)) => {
cache.package_status = all_upgradeable;
cache
}
_ => PkgState {
notified: None,
package_status: all_upgradeable,
},
};
write_pkg_cache(&cache)?;
Ok(cache)
}
const_regex! {
VERSION_EPOCH_REGEX = r"^\d+:";
FILENAME_EXTRACT_REGEX = r"^.*/.*?_(.*)_Packages$";
}
pub struct FilterData<'a> {
/// package name
pub package: &'a str,
/// this is version info returned by APT
pub installed_version: Option<&'a str>,
pub candidate_version: &'a str,
/// this is the version info the filter is supposed to check
pub active_version: &'a str,
}
enum PackagePreSelect {
OnlyInstalled,
OnlyNew,
All,
}
pub fn list_installed_apt_packages<F: Fn(FilterData) -> bool>(
filter: F,
only_versions_for: Option<&str>,
) -> Vec<APTUpdateInfo> {
let mut ret = Vec::new();
let mut depends = HashSet::new();
// note: this is not an 'apt update', it just re-reads the cache from disk
let mut cache = Cache::get_singleton();
cache.reload();
let mut cache_iter = match only_versions_for {
Some(name) => cache.find_by_name(name),
None => cache.iter(),
};
loop {
match cache_iter.next() {
Some(view) => {
let di = if only_versions_for.is_some() {
query_detailed_info(PackagePreSelect::All, &filter, view, None)
} else {
query_detailed_info(
PackagePreSelect::OnlyInstalled,
&filter,
view,
Some(&mut depends),
)
};
if let Some(info) = di {
ret.push(info);
}
if only_versions_for.is_some() {
break;
}
}
None => {
drop(cache_iter);
// also loop through missing dependencies, as they would be installed
for pkg in depends.iter() {
let mut iter = cache.find_by_name(pkg);
let view = match iter.next() {
Some(view) => view,
None => continue, // package not found, ignore
};
let di = query_detailed_info(PackagePreSelect::OnlyNew, &filter, view, None);
if let Some(info) = di {
ret.push(info);
}
}
break;
}
}
}
ret
}
fn query_detailed_info<'a, F, V>(
pre_select: PackagePreSelect,
filter: F,
view: V,
depends: Option<&mut HashSet<String>>,
) -> Option<APTUpdateInfo>
where
F: Fn(FilterData) -> bool,
V: std::ops::Deref<Target = apt_pkg_native::sane::PkgView<'a>>,
{
let current_version = view.current_version();
let candidate_version = view.candidate_version();
let (current_version, candidate_version) = match pre_select {
PackagePreSelect::OnlyInstalled => match (current_version, candidate_version) {
(Some(cur), Some(can)) => (Some(cur), can), // package installed and there is an update
(Some(cur), None) => (Some(cur.clone()), cur), // package installed and up-to-date
(None, Some(_)) => return None, // package could be installed
(None, None) => return None, // broken
},
PackagePreSelect::OnlyNew => match (current_version, candidate_version) {
(Some(_), Some(_)) => return None,
(Some(_), None) => return None,
(None, Some(can)) => (None, can),
(None, None) => return None,
},
PackagePreSelect::All => match (current_version, candidate_version) {
(Some(cur), Some(can)) => (Some(cur), can),
(Some(cur), None) => (Some(cur.clone()), cur),
(None, Some(can)) => (None, can),
(None, None) => return None,
},
};
// get additional information via nested APT 'iterators'
let mut view_iter = view.versions();
while let Some(ver) = view_iter.next() {
let package = view.name();
let version = ver.version();
let mut origin_res = "unknown".to_owned();
let mut section_res = "unknown".to_owned();
let mut priority_res = "unknown".to_owned();
let mut short_desc = package.clone();
let mut long_desc = "".to_owned();
let fd = FilterData {
package: package.as_str(),
installed_version: current_version.as_deref(),
candidate_version: &candidate_version,
active_version: &version,
};
if filter(fd) {
if let Some(section) = ver.section() {
section_res = section;
}
if let Some(prio) = ver.priority_type() {
priority_res = prio;
}
// assume every package has only one origin file (not
// origin, but origin *file*, for some reason those seem to
// be different concepts in APT)
let mut origin_iter = ver.origin_iter();
let origin = origin_iter.next();
if let Some(origin) = origin {
if let Some(sd) = origin.short_desc() {
short_desc = sd;
}
if let Some(ld) = origin.long_desc() {
long_desc = ld;
}
// the package files appear in priority order, meaning
// the one for the candidate version is first - this is fine
// however, as the source package should be the same for all
// versions anyway
let mut pkg_iter = origin.file();
let pkg_file = pkg_iter.next();
if let Some(pkg_file) = pkg_file {
if let Some(origin_name) = pkg_file.origin() {
origin_res = origin_name;
}
}
}
if let Some(depends) = depends {
let mut dep_iter = ver.dep_iter();
loop {
let dep = match dep_iter.next() {
Some(dep) if dep.dep_type() != "Depends" => continue,
Some(dep) => dep,
None => break,
};
let dep_pkg = dep.target_pkg();
let name = dep_pkg.name();
depends.insert(name);
}
}
return Some(APTUpdateInfo {
package,
title: short_desc,
arch: view.arch(),
description: long_desc,
origin: origin_res,
version: candidate_version.clone(),
old_version: match current_version {
Some(vers) => vers,
None => "".to_owned(),
},
priority: priority_res,
section: section_res,
extra_info: None,
});
}
}
None
}

View File

@ -6,7 +6,6 @@ use anyhow::{bail, Error};
use proxmox_http::{client::Client, HttpOptions, ProxyConfig};
pub mod apt;
pub mod config;
pub mod disks;
pub mod fs;