all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Wolfgang Bumiller <w.bumiller@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH proxmox 5/6] apt: add cache feature
Date: Tue,  9 Jul 2024 08:18:31 +0200	[thread overview]
Message-ID: <20240709061832.52121-5-w.bumiller@proxmox.com> (raw)
In-Reply-To: <20240709061832.52121-1-w.bumiller@proxmox.com>

From: Dietmar Maurer <dietmar@proxmox.com>

Save/read package state from a file, and add the api functions to manipulate
that state.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
---
 proxmox-apt/Cargo.toml            |  17 ++
 proxmox-apt/debian/control        |  23 +++
 proxmox-apt/src/api.rs            | 143 ++++++++++++++
 proxmox-apt/src/cache.rs          | 301 ++++++++++++++++++++++++++++++
 proxmox-apt/src/cache_api.rs      | 208 +++++++++++++++++++++
 proxmox-apt/src/lib.rs            |   9 +
 proxmox-apt/tests/repositories.rs |   8 +-
 7 files changed, 706 insertions(+), 3 deletions(-)
 create mode 100644 proxmox-apt/src/api.rs
 create mode 100644 proxmox-apt/src/cache.rs
 create mode 100644 proxmox-apt/src/cache_api.rs

diff --git a/proxmox-apt/Cargo.toml b/proxmox-apt/Cargo.toml
index bbd4ff89..923be446 100644
--- a/proxmox-apt/Cargo.toml
+++ b/proxmox-apt/Cargo.toml
@@ -22,3 +22,20 @@ rfc822-like = "0.2.1"
 
 proxmox-apt-api-types.workspace = true
 proxmox-config-digest = { workspace = true, features = ["openssl"] }
+proxmox-sys.workspace = true
+
+apt-pkg-native = { version = "0.3.2", optional = true }
+regex = { workspace = true, optional = true }
+nix = { workspace = true, optional = true }
+log = { workspace = true, optional = true }
+proxmox-schema = { workspace = true, optional = true }
+
+[features]
+default = []
+cache = [
+    "dep:apt-pkg-native",
+    "dep:regex",
+    "dep:nix",
+    "dep:log",
+    "dep:proxmox-schema",
+]
diff --git a/proxmox-apt/debian/control b/proxmox-apt/debian/control
index 347631e6..7e0b79b1 100644
--- a/proxmox-apt/debian/control
+++ b/proxmox-apt/debian/control
@@ -13,6 +13,7 @@ Build-Depends: debhelper (>= 12),
  librust-proxmox-apt-api-types-1+default-dev <!nocheck>,
  librust-proxmox-config-digest-0.1+default-dev <!nocheck>,
  librust-proxmox-config-digest-0.1+openssl-dev <!nocheck>,
+ librust-proxmox-sys-0.5+default-dev (>= 0.5.7-~~) <!nocheck>,
  librust-rfc822-like-0.2+default-dev (>= 0.2.1-~~) <!nocheck>,
  librust-serde-1+default-dev <!nocheck>,
  librust-serde-1+derive-dev <!nocheck>,
@@ -37,10 +38,13 @@ Depends:
  librust-proxmox-apt-api-types-1+default-dev,
  librust-proxmox-config-digest-0.1+default-dev,
  librust-proxmox-config-digest-0.1+openssl-dev,
+ librust-proxmox-sys-0.5+default-dev (>= 0.5.7-~~),
  librust-rfc822-like-0.2+default-dev (>= 0.2.1-~~),
  librust-serde-1+default-dev,
  librust-serde-1+derive-dev,
  librust-serde-json-1+default-dev
+Suggests:
+ librust-proxmox-apt+cache-dev (= ${binary:Version})
 Provides:
  librust-proxmox-apt+default-dev (= ${binary:Version}),
  librust-proxmox-apt-0-dev (= ${binary:Version}),
@@ -51,3 +55,22 @@ Provides:
  librust-proxmox-apt-0.10.10+default-dev (= ${binary:Version})
 Description: Proxmox library for APT - Rust source code
  Source code for Debianized Rust crate "proxmox-apt"
+
+Package: librust-proxmox-apt+cache-dev
+Architecture: any
+Multi-Arch: same
+Depends:
+ ${misc:Depends},
+ librust-proxmox-apt-dev (= ${binary:Version}),
+ librust-apt-pkg-native-0.3+default-dev (>= 0.3.2-~~),
+ librust-log-0.4+default-dev (>= 0.4.17-~~),
+ librust-nix-0.26+default-dev (>= 0.26.1-~~),
+ librust-proxmox-schema-3+default-dev (>= 3.1.1-~~),
+ librust-regex-1+default-dev (>= 1.5-~~)
+Provides:
+ librust-proxmox-apt-0+cache-dev (= ${binary:Version}),
+ librust-proxmox-apt-0.10+cache-dev (= ${binary:Version}),
+ librust-proxmox-apt-0.10.10+cache-dev (= ${binary:Version})
+Description: Proxmox library for APT - feature "cache"
+ This metapackage enables feature "cache" for the Rust proxmox-apt crate, by
+ pulling in any additional dependencies needed by that feature.
diff --git a/proxmox-apt/src/api.rs b/proxmox-apt/src/api.rs
new file mode 100644
index 00000000..af01048e
--- /dev/null
+++ b/proxmox-apt/src/api.rs
@@ -0,0 +1,143 @@
+// API function that work without feature "cache"
+
+use anyhow::{bail, Error};
+
+use proxmox_apt_api_types::{
+    APTChangeRepositoryOptions, APTGetChangelogOptions, APTRepositoriesResult, APTRepositoryFile,
+    APTRepositoryHandle,
+};
+use proxmox_config_digest::ConfigDigest;
+
+use crate::repositories::{APTRepositoryFileImpl, APTRepositoryImpl};
+
+/// Retrieve the changelog of the specified package.
+pub fn get_changelog(options: &APTGetChangelogOptions) -> Result<String, 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) = &options.version {
+        command.arg(format!("{}={}", options.name, ver));
+    } else {
+        command.arg(&options.name);
+    }
+    let output = proxmox_sys::command::run_command(command, None)?;
+
+    Ok(output)
+}
+
+/// Get APT repository information.
+pub fn list_repositories(product: &str) -> Result<APTRepositoriesResult, Error> {
+    let (files, errors, digest) = crate::repositories::repositories()?;
+
+    let suite = crate::repositories::get_current_release_codename()?;
+
+    let infos = crate::repositories::check_repositories(&files, suite);
+    let standard_repos = crate::repositories::standard_repositories(&files, product, suite);
+
+    Ok(APTRepositoriesResult {
+        files,
+        errors,
+        digest,
+        infos,
+        standard_repos,
+    })
+}
+
+/// Add the repository identified by the `handle`.
+/// 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(
+    product: &str,
+    handle: APTRepositoryHandle,
+    digest: Option<ConfigDigest>,
+) -> Result<(), Error> {
+    let (mut files, errors, current_digest) = crate::repositories::repositories()?;
+
+    current_digest.detect_modification(digest.as_ref())?;
+
+    let suite = crate::repositories::get_current_release_codename()?;
+
+    // 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) = crate::repositories::get_standard_repository(handle, product, 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(())
+}
+
+/// Change the properties of the specified repository.
+///
+/// The `digest` parameter asserts that the configuration has not been modified.
+pub fn change_repository(
+    path: &str,
+    index: usize,
+    options: &APTChangeRepositoryOptions,
+    digest: Option<ConfigDigest>,
+) -> Result<(), Error> {
+    let (mut files, errors, current_digest) = crate::repositories::repositories()?;
+
+    current_digest.detect_modification(digest.as_ref())?;
+
+    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_deref() == Some(path))
+    {
+        if let Some(repo) = file.repositories.get_mut(index) {
+            if let Some(enabled) = options.enabled {
+                repo.set_enabled(enabled);
+            }
+
+            file.write()?;
+        } else {
+            bail!("invalid index - {}", index);
+        }
+    } else {
+        bail!("invalid path - {}", path);
+    }
+
+    Ok(())
+}
diff --git a/proxmox-apt/src/cache.rs b/proxmox-apt/src/cache.rs
new file mode 100644
index 00000000..03315013
--- /dev/null
+++ b/proxmox-apt/src/cache.rs
@@ -0,0 +1,301 @@
+use std::collections::HashMap;
+use std::collections::HashSet;
+use std::path::Path;
+
+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 proxmox_apt_api_types::APTUpdateInfo;
+
+#[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<P: AsRef<Path>>(apt_state_file: P, state: &PkgState) -> Result<(), Error> {
+    let serialized_state = serde_json::to_string(state)?;
+
+    replace_file(
+        apt_state_file,
+        serialized_state.as_bytes(),
+        CreateOptions::new(),
+        false,
+    )
+    .map_err(|err| format_err!("Error writing package cache - {}", err))?;
+    Ok(())
+}
+
+pub fn read_pkg_state<P: AsRef<Path>>(apt_state_file: P) -> Result<Option<PkgState>, Error> {
+    let serialized_state = match file_read_optional_string(apt_state_file) {
+        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<P: AsRef<Path>>(apt_state_file: P) -> Result<bool, Error> {
+    if let Ok(pbs_cache) = std::fs::metadata(apt_state_file) {
+        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<P: AsRef<Path>>(apt_state_file: P) -> Result<PkgState, Error> {
+    let apt_state_file = apt_state_file.as_ref();
+    // 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(apt_state_file) {
+        Ok(Some(mut cache)) => {
+            cache.package_status = all_upgradeable;
+            cache
+        }
+        _ => PkgState {
+            notified: None,
+            package_status: all_upgradeable,
+        },
+    };
+    write_pkg_cache(apt_state_file, &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
+}
+
+pub fn sort_package_list(packages: &mut Vec<APTUpdateInfo>) {
+    let cache = apt_pkg_native::Cache::get_singleton();
+    packages.sort_by(|left, right| {
+        cache
+            .compare_versions(&left.old_version, &right.old_version)
+            .reverse()
+    });
+}
diff --git a/proxmox-apt/src/cache_api.rs b/proxmox-apt/src/cache_api.rs
new file mode 100644
index 00000000..979f47ed
--- /dev/null
+++ b/proxmox-apt/src/cache_api.rs
@@ -0,0 +1,208 @@
+// API function that need feature "cache"
+use std::path::Path;
+
+use anyhow::{bail, format_err, Error};
+use std::os::unix::prelude::OsStrExt;
+
+use proxmox_apt_api_types::{APTUpdateInfo, APTUpdateOptions};
+
+/// List available APT updates
+///
+/// Automatically updates an expired package cache.
+pub fn list_available_apt_update<P: AsRef<Path>>(
+    apt_state_file: P,
+) -> Result<Vec<APTUpdateInfo>, Error> {
+    let apt_state_file = apt_state_file.as_ref();
+    if let Ok(false) = crate::cache::pkg_cache_expired(apt_state_file) {
+        if let Ok(Some(cache)) = crate::cache::read_pkg_state(apt_state_file) {
+            return Ok(cache.package_status);
+        }
+    }
+
+    let cache = crate::cache::update_cache(apt_state_file)?;
+
+    Ok(cache.package_status)
+}
+
+/// Update the APT database
+///
+/// You should update the APT proxy configuration before running this.
+pub fn update_database<P: AsRef<Path>>(
+    apt_state_file: P,
+    options: &APTUpdateOptions,
+    send_updates_available: impl Fn(&[&APTUpdateInfo]) -> Result<(), Error>,
+) -> Result<(), Error> {
+    let apt_state_file = apt_state_file.as_ref();
+
+    let quiet = options.quiet.unwrap_or(false);
+    let notify = options.notify.unwrap_or(false);
+
+    if !quiet {
+        log::info!("starting apt-get update")
+    }
+
+    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 {
+        log::info!("{}", 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)"));
+            log::warn!("{msg}");
+        } else {
+            bail!("terminated by signal");
+        }
+    }
+
+    let mut cache = crate::cache::update_cache(apt_state_file)?;
+
+    if notify {
+        let mut notified = match cache.notified {
+            Some(notified) => notified,
+            None => std::collections::HashMap::new(),
+        };
+        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);
+            send_updates_available(&to_notify)?;
+        }
+        cache.notified = Some(notified);
+        crate::cache::write_pkg_cache(apt_state_file, &cache)?;
+    }
+
+    Ok(())
+}
+
+/// Get package information for a list of important product packages.
+///
+/// We first list the product virtual package (i.e. `proxmox-backup`), with extra
+/// information about the running kernel.
+///
+/// Next is the api_server_package, with extra information abnout the running api
+/// server version.
+///
+/// The list of installed kernel packages follows.
+///
+/// We the add an entry for all packages in package_list, even if they are
+/// not installed.
+pub fn get_package_versions(
+    product_virtual_package: &str,
+    api_server_package: &str,
+    running_api_server_version: &str,
+    package_list: &[&str],
+) -> Result<Vec<APTUpdateInfo>, Error> {
+    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 mut packages: Vec<APTUpdateInfo> = Vec::new();
+
+    let is_kernel =
+        |name: &str| name.starts_with("pve-kernel-") || name.starts_with("proxmox-kernel");
+
+    let installed_packages = crate::cache::list_installed_apt_packages(
+        |filter| {
+            filter.installed_version == Some(filter.active_version)
+                && (is_kernel(filter.package)
+                    || (filter.package == product_virtual_package)
+                    || (filter.package == api_server_package)
+                    || package_list.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(product_virtual_package_info) = installed_packages
+        .iter()
+        .find(|pkg| pkg.package == product_virtual_package)
+    {
+        let mut product_virtual_package_info = product_virtual_package_info.clone();
+        product_virtual_package_info.extra_info = Some(running_kernel);
+        packages.push(product_virtual_package_info);
+    } else {
+        packages.push(unknown_package(
+            product_virtual_package.into(),
+            Some(running_kernel),
+        ));
+    }
+
+    if let Some(api_server_package_info) = installed_packages
+        .iter()
+        .find(|pkg| pkg.package == api_server_package)
+    {
+        let mut api_server_package_info = api_server_package_info.clone();
+        api_server_package_info.extra_info = Some(running_api_server_version.into());
+        packages.push(api_server_package_info);
+    } else {
+        packages.push(unknown_package(
+            api_server_package.into(),
+            Some(running_api_server_version.into()),
+        ));
+    }
+
+    let mut kernel_pkgs: Vec<APTUpdateInfo> = installed_packages
+        .iter()
+        .filter(|pkg| is_kernel(&pkg.package))
+        .cloned()
+        .collect();
+
+    crate::cache::sort_package_list(&mut kernel_pkgs);
+
+    packages.append(&mut kernel_pkgs);
+
+    // add entry for all packages we're interested in, even if not installed
+    for pkg in package_list.iter() {
+        if *pkg == product_virtual_package || *pkg == api_server_package {
+            continue;
+        }
+        match installed_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)
+}
diff --git a/proxmox-apt/src/lib.rs b/proxmox-apt/src/lib.rs
index 60bf1d2a..f25ac90b 100644
--- a/proxmox-apt/src/lib.rs
+++ b/proxmox-apt/src/lib.rs
@@ -1,5 +1,14 @@
 #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
 
+mod api;
+pub use api::{add_repository_handle, change_repository, get_changelog, list_repositories};
+
+#[cfg(feature = "cache")]
+pub mod cache;
+#[cfg(feature = "cache")]
+mod cache_api;
+#[cfg(feature = "cache")]
+pub use cache_api::{get_package_versions, list_available_apt_update, update_database};
 pub mod config;
 pub mod deb822;
 pub mod repositories;
diff --git a/proxmox-apt/tests/repositories.rs b/proxmox-apt/tests/repositories.rs
index 228ef696..e4a94525 100644
--- a/proxmox-apt/tests/repositories.rs
+++ b/proxmox-apt/tests/repositories.rs
@@ -5,11 +5,13 @@ use anyhow::{bail, format_err, Error};
 use proxmox_apt::config::APTConfig;
 
 use proxmox_apt::repositories::{
-    check_repositories, get_current_release_codename, standard_repositories, APTRepositoryFile,
-    APTRepositoryHandle, APTRepositoryInfo, APTStandardRepository, DebianCodename,
+    check_repositories, get_current_release_codename, standard_repositories, DebianCodename,
 };
 use proxmox_apt::repositories::{
-    APTRepositoryFileImpl, APTRepositoryHandleImpl, APTRepositoryImpl, APTStandardRepositoryImpl,
+    APTRepositoryFileImpl, APTRepositoryImpl, APTStandardRepositoryImpl,
+};
+use proxmox_apt_api_types::{
+    APTRepositoryFile, APTRepositoryHandle, APTRepositoryInfo, APTStandardRepository,
 };
 
 fn create_clean_directory(path: &PathBuf) -> Result<(), Error> {
-- 
2.39.2



_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel


  parent reply	other threads:[~2024-07-09  6:18 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-07-09  6:18 [pbs-devel] applied-series: [PATCH proxmox 1/6] apt-api-types: new crate Wolfgang Bumiller
2024-07-09  6:18 ` [pbs-devel] [PATCH proxmox 2/6] apt-api-types: use serde-plain to display/parse enums Wolfgang Bumiller
2024-07-09  6:18 ` [pbs-devel] [PATCH proxmox 3/6] apt: avoid direct impl on api types (use traits instead) Wolfgang Bumiller
2024-07-09  6:18 ` [pbs-devel] [PATCH proxmox 4/6] apt: use api types from apt-api-types crate Wolfgang Bumiller
2024-07-09  6:18 ` Wolfgang Bumiller [this message]
2024-07-09  6:18 ` [pbs-devel] [PATCH proxmox 6/6] apt: avoid global apt config Wolfgang Bumiller
2024-07-09 10:34   ` Fiona Ebner
2024-07-09 10:48     ` Wolfgang Bumiller

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=20240709061832.52121-5-w.bumiller@proxmox.com \
    --to=w.bumiller@proxmox.com \
    --cc=pbs-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