* [pbs-devel] applied-series: [PATCH proxmox 1/6] apt-api-types: new crate
@ 2024-07-09 6:18 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
` (4 more replies)
0 siblings, 5 replies; 8+ messages in thread
From: Wolfgang Bumiller @ 2024-07-09 6:18 UTC (permalink / raw)
To: pbs-devel
From: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
---
Cargo.toml | 2 +
proxmox-apt-api-types/Cargo.toml | 15 +
proxmox-apt-api-types/debian/changelog | 5 +
proxmox-apt-api-types/debian/control | 42 ++
proxmox-apt-api-types/debian/copyright | 18 +
proxmox-apt-api-types/debian/debcargo.toml | 7 +
proxmox-apt-api-types/src/lib.rs | 442 +++++++++++++++++++++
7 files changed, 531 insertions(+)
create mode 100644 proxmox-apt-api-types/Cargo.toml
create mode 100644 proxmox-apt-api-types/debian/changelog
create mode 100644 proxmox-apt-api-types/debian/control
create mode 100644 proxmox-apt-api-types/debian/copyright
create mode 100644 proxmox-apt-api-types/debian/debcargo.toml
create mode 100644 proxmox-apt-api-types/src/lib.rs
diff --git a/Cargo.toml b/Cargo.toml
index 48fa77b6..15556670 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,6 +5,7 @@ members = [
"proxmox-acme-api",
"proxmox-api-macro",
"proxmox-apt",
+ "proxmox-apt-api-types",
"proxmox-async",
"proxmox-auth-api",
"proxmox-borrow",
@@ -111,6 +112,7 @@ zstd = { version = "0.12", features = [ "bindgen" ] }
# workspace dependencies
proxmox-acme = { version = "0.5.2", path = "proxmox-acme", default-features = false }
proxmox-api-macro = { version = "1.0.8", path = "proxmox-api-macro" }
+proxmox-apt-api-types = { version = "1.0.0", path = "proxmox-apt-api-types" }
proxmox-auth-api = { version = "0.4.0", path = "proxmox-auth-api" }
proxmox-async = { version = "0.4.1", path = "proxmox-async" }
proxmox-compression = { version = "0.2.0", path = "proxmox-compression" }
diff --git a/proxmox-apt-api-types/Cargo.toml b/proxmox-apt-api-types/Cargo.toml
new file mode 100644
index 00000000..e2ab46ad
--- /dev/null
+++ b/proxmox-apt-api-types/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "proxmox-apt-api-types"
+version = "1.0.0"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+exclude.workspace = true
+description = "APT API type definitions."
+
+[dependencies]
+anyhow.workspace = true
+serde = { workspace = true, features = ["derive"] }
+proxmox-schema = { workspace = true, features = ["api-macro"] }
+proxmox-config-digest.workspace = true
diff --git a/proxmox-apt-api-types/debian/changelog b/proxmox-apt-api-types/debian/changelog
new file mode 100644
index 00000000..32221867
--- /dev/null
+++ b/proxmox-apt-api-types/debian/changelog
@@ -0,0 +1,5 @@
+rust-proxmox-apt-api-types (1.0.0-1) bookworm; urgency=medium
+
+ * initial release
+
+ -- Proxmox Support Team <support@proxmox.com> Thu, 27 Jun 2024 13:14:23 +0200
diff --git a/proxmox-apt-api-types/debian/control b/proxmox-apt-api-types/debian/control
new file mode 100644
index 00000000..708e8c4b
--- /dev/null
+++ b/proxmox-apt-api-types/debian/control
@@ -0,0 +1,42 @@
+Source: rust-proxmox-apt-api-types
+Section: rust
+Priority: optional
+Build-Depends: debhelper (>= 12),
+ dh-cargo (>= 25),
+ cargo:native <!nocheck>,
+ rustc:native <!nocheck>,
+ libstd-rust-dev <!nocheck>,
+ librust-anyhow-1+default-dev <!nocheck>,
+ librust-proxmox-config-digest-0.1+default-dev <!nocheck>,
+ librust-proxmox-schema-3+api-macro-dev (>= 3.1.1-~~) <!nocheck>,
+ librust-proxmox-schema-3+default-dev (>= 3.1.1-~~) <!nocheck>,
+ librust-serde-1+default-dev <!nocheck>,
+ librust-serde-1+derive-dev <!nocheck>
+Maintainer: Proxmox Support Team <support@proxmox.com>
+Standards-Version: 4.6.2
+Vcs-Git: git://git.proxmox.com/git/proxmox-apt.git
+Vcs-Browser: https://git.proxmox.com/?p=proxmox-apt.git
+X-Cargo-Crate: proxmox-apt-api-types
+Rules-Requires-Root: no
+
+Package: librust-proxmox-apt-api-types-dev
+Architecture: any
+Multi-Arch: same
+Depends:
+ ${misc:Depends},
+ librust-anyhow-1+default-dev,
+ librust-proxmox-config-digest-0.1+default-dev,
+ librust-proxmox-schema-3+api-macro-dev (>= 3.1.1-~~),
+ librust-proxmox-schema-3+default-dev (>= 3.1.1-~~),
+ librust-serde-1+default-dev,
+ librust-serde-1+derive-dev
+Provides:
+ librust-proxmox-apt-api-types+default-dev (= ${binary:Version}),
+ librust-proxmox-apt-api-types-1-dev (= ${binary:Version}),
+ librust-proxmox-apt-api-types-1+default-dev (= ${binary:Version}),
+ librust-proxmox-apt-api-types-1.0-dev (= ${binary:Version}),
+ librust-proxmox-apt-api-types-1.0+default-dev (= ${binary:Version}),
+ librust-proxmox-apt-api-types-1.0.0-dev (= ${binary:Version}),
+ librust-proxmox-apt-api-types-1.0.0+default-dev (= ${binary:Version})
+Description: APT API type definitions - Rust source code
+ Source code for Debianized Rust crate "proxmox-apt-api-types"
diff --git a/proxmox-apt-api-types/debian/copyright b/proxmox-apt-api-types/debian/copyright
new file mode 100644
index 00000000..b227c290
--- /dev/null
+++ b/proxmox-apt-api-types/debian/copyright
@@ -0,0 +1,18 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+
+Files:
+ *
+Copyright: 2019 - 2024 Proxmox Server Solutions GmbH <support@proxmox.com>
+License: AGPL-3.0-or-later
+ This program is free software: you can redistribute it and/or modify it under
+ the terms of the GNU Affero General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option) any
+ later version.
+ .
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ details.
+ .
+ You should have received a copy of the GNU Affero General Public License along
+ with this program. If not, see <https://www.gnu.org/licenses/>.
diff --git a/proxmox-apt-api-types/debian/debcargo.toml b/proxmox-apt-api-types/debian/debcargo.toml
new file mode 100644
index 00000000..74e38540
--- /dev/null
+++ b/proxmox-apt-api-types/debian/debcargo.toml
@@ -0,0 +1,7 @@
+overlay = "."
+crate_src_path = ".."
+maintainer = "Proxmox Support Team <support@proxmox.com>"
+
+[source]
+vcs_git = "git://git.proxmox.com/git/proxmox-apt.git"
+vcs_browser = "https://git.proxmox.com/?p=proxmox-apt.git"
diff --git a/proxmox-apt-api-types/src/lib.rs b/proxmox-apt-api-types/src/lib.rs
new file mode 100644
index 00000000..3b6ac9e4
--- /dev/null
+++ b/proxmox-apt-api-types/src/lib.rs
@@ -0,0 +1,442 @@
+use std::fmt::Display;
+
+use anyhow::{bail, Error};
+use serde::{Deserialize, Serialize};
+
+use proxmox_config_digest::ConfigDigest;
+use proxmox_schema::api;
+
+#[api]
+#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "lowercase")]
+pub enum APTRepositoryFileType {
+ /// One-line-style format
+ List,
+ /// DEB822-style format
+ Sources,
+}
+
+impl TryFrom<&str> for APTRepositoryFileType {
+ type Error = Error;
+
+ fn try_from(file_type: &str) -> Result<Self, Error> {
+ match file_type {
+ "list" => Ok(APTRepositoryFileType::List),
+ "sources" => Ok(APTRepositoryFileType::Sources),
+ _ => bail!("invalid file type '{file_type}'"),
+ }
+ }
+}
+
+impl Display for APTRepositoryFileType {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ APTRepositoryFileType::List => write!(f, "list"),
+ APTRepositoryFileType::Sources => write!(f, "sources"),
+ }
+ }
+}
+
+#[api]
+#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "kebab-case")]
+pub enum APTRepositoryPackageType {
+ /// Debian package
+ Deb,
+ /// Debian source package
+ DebSrc,
+}
+
+impl TryFrom<&str> for APTRepositoryPackageType {
+ type Error = Error;
+
+ fn try_from(package_type: &str) -> Result<Self, Error> {
+ match package_type {
+ "deb" => Ok(APTRepositoryPackageType::Deb),
+ "deb-src" => Ok(APTRepositoryPackageType::DebSrc),
+ _ => bail!("invalid package type '{package_type}'"),
+ }
+ }
+}
+
+impl Display for APTRepositoryPackageType {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ APTRepositoryPackageType::Deb => write!(f, "deb"),
+ APTRepositoryPackageType::DebSrc => write!(f, "deb-src"),
+ }
+ }
+}
+
+#[api(
+ properties: {
+ Key: {
+ description: "Option key.",
+ type: String,
+ },
+ Values: {
+ description: "Option values.",
+ type: Array,
+ items: {
+ description: "Value.",
+ type: String,
+ },
+ },
+ },
+)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "PascalCase")] // for consistency
+/// Additional options for an APT repository.
+/// Used for both single- and mutli-value options.
+pub struct APTRepositoryOption {
+ /// Option key.
+ pub key: String,
+ /// Option value(s).
+ pub values: Vec<String>,
+}
+
+#[api(
+ properties: {
+ Types: {
+ description: "List of package types.",
+ type: Array,
+ items: {
+ type: APTRepositoryPackageType,
+ },
+ },
+ URIs: {
+ description: "List of repository URIs.",
+ type: Array,
+ items: {
+ description: "Repository URI.",
+ type: String,
+ },
+ },
+ Suites: {
+ description: "List of distributions.",
+ type: Array,
+ items: {
+ description: "Package distribution.",
+ type: String,
+ },
+ },
+ Components: {
+ description: "List of repository components.",
+ type: Array,
+ items: {
+ description: "Repository component.",
+ type: String,
+ },
+ },
+ Options: {
+ type: Array,
+ optional: true,
+ items: {
+ type: APTRepositoryOption,
+ },
+ },
+ Comment: {
+ description: "Associated comment.",
+ type: String,
+ optional: true,
+ },
+ FileType: {
+ type: APTRepositoryFileType,
+ },
+ Enabled: {
+ description: "Whether the repository is enabled or not.",
+ type: Boolean,
+ },
+ },
+)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "PascalCase")]
+/// Describes an APT repository.
+pub struct APTRepository {
+ /// List of package types.
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub types: Vec<APTRepositoryPackageType>,
+
+ /// List of repository URIs.
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ #[serde(rename = "URIs")]
+ pub uris: Vec<String>,
+
+ /// List of package distributions.
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub suites: Vec<String>,
+
+ /// List of repository components.
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub components: Vec<String>,
+
+ /// Additional options.
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub options: Vec<APTRepositoryOption>,
+
+ /// Associated comment.
+ #[serde(default, skip_serializing_if = "String::is_empty")]
+ pub comment: String,
+
+ /// Format of the defining file.
+ pub file_type: APTRepositoryFileType,
+
+ /// Whether the repository is enabled or not.
+ pub enabled: bool,
+}
+
+#[api(
+ properties: {
+ "file-type": {
+ type: APTRepositoryFileType,
+ },
+ repositories: {
+ description: "List of APT repositories.",
+ type: Array,
+ items: {
+ type: APTRepository,
+ },
+ },
+ digest: {
+ type: ConfigDigest,
+ optional: true,
+ },
+ },
+)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+/// Represents an abstract APT repository file.
+pub struct APTRepositoryFile {
+ /// The path to the file. If None, `contents` must be set directly.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub path: Option<String>,
+
+ /// The type of the file.
+ pub file_type: APTRepositoryFileType,
+
+ /// List of repositories in the file.
+ pub repositories: Vec<APTRepository>,
+
+ /// The file content, if already parsed.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub content: Option<String>,
+
+ /// Digest of the original contents.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub digest: Option<ConfigDigest>,
+}
+
+#[api]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+/// Error type for problems with APT repository files.
+pub struct APTRepositoryFileError {
+ /// The path to the problematic file.
+ pub path: String,
+
+ /// The error message.
+ pub error: String,
+}
+
+impl Display for APTRepositoryFileError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "proxmox-apt error for '{}' - {}", self.path, self.error)
+ }
+}
+
+impl std::error::Error for APTRepositoryFileError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ None
+ }
+}
+
+#[api]
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+/// Additional information for a repository.
+pub struct APTRepositoryInfo {
+ /// Path to the defining file.
+ #[serde(default, skip_serializing_if = "String::is_empty")]
+ pub path: String,
+
+ /// Index of the associated respository within the file (starting from 0).
+ pub index: usize,
+
+ /// The property from which the info originates (e.g. "Suites")
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub property: Option<String>,
+
+ /// Info kind (e.g. "warning")
+ pub kind: String,
+
+ /// Info message
+ pub message: String,
+}
+
+#[api(
+ properties: {
+ handle: {
+ description: "Handle referencing a standard repository.",
+ type: String,
+ },
+ },
+)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+#[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, where `None` means
+ /// not configured, and `Some(bool)` indicates enabled or disabled.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub status: Option<bool>,
+
+ /// Display name of the repository.
+ pub name: String,
+
+ /// Description of the repository.
+ pub description: String,
+}
+
+#[api]
+#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
+#[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 Quincy enterprise repository.
+ CephQuincyEnterprise,
+ /// Ceph Quincy no-subscription repository.
+ CephQuincyNoSubscription,
+ /// Ceph Quincy test repository.
+ CephQuincyTest,
+ // TODO: Add separate enum for ceph releases and use something like
+ // `CephTest(CephReleaseCodename),` once the API macro supports it.
+ /// Ceph Reef enterprise repository.
+ CephReefEnterprise,
+ /// Ceph Reef no-subscription repository.
+ CephReefNoSubscription,
+ /// Ceph Reef test repository.
+ CephReefTest,
+}
+
+#[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(
+ properties: {
+ notify: {
+ default: false,
+ optional: true,
+ },
+ quiet: {
+ default: false,
+ optional: true,
+ },
+ }
+)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+/// Options for APT update
+pub struct APTUpdateOptions {
+ /// Send notification mail about new package updates available to the email
+ /// address configured for 'root@pam').
+ pub notify: Option<bool>,
+ /// Only produces output suitable for logging, omitting progress indicators.
+ pub quiet: Option<bool>,
+}
+
+#[api(
+ properties: {
+ files: {
+ type: Array,
+ items: {
+ type: APTRepositoryFile,
+ },
+ },
+ errors: {
+ type: Array,
+ items: {
+ type: APTRepositoryFileError,
+ },
+ },
+ infos: {
+ type: Array,
+ items: {
+ type: APTRepositoryInfo,
+ },
+ },
+ "standard-repos": {
+ type: Array,
+ items: {
+ type: APTStandardRepository,
+ },
+ },
+ digest: {
+ type: ConfigDigest,
+ },
+ },
+)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+/// Result from parsing the APT repository files in /etc/apt/.
+pub struct APTRepositoriesResult {
+ /// List of problematic files.
+ pub errors: Vec<APTRepositoryFileError>,
+ /// List of standard repositories and their configuration status.
+ pub standard_repos: Vec<APTStandardRepository>,
+ /// List of additional information/warnings about the repositories
+ pub infos: Vec<APTRepositoryInfo>,
+ /// List of parsed repository files.
+ pub files: Vec<APTRepositoryFile>,
+ pub digest: ConfigDigest,
+}
+
+#[api()]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+/// Options for the get changelog API.
+pub struct APTGetChangelogOptions {
+ /// Package name to get changelog of.
+ pub name: String,
+ /// Package version to get changelog of. Omit to use candidate version.
+ pub version: Option<String>,
+}
+
+#[api()]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+/// Options for the change repository API call
+pub struct APTChangeRepositoryOptions {
+ /// Whether the repository should be enabled or not.
+ pub enabled: Option<bool>,
+}
--
2.39.2
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 8+ messages in thread
* [pbs-devel] [PATCH proxmox 2/6] apt-api-types: use serde-plain to display/parse enums
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 ` 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
` (3 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Wolfgang Bumiller @ 2024-07-09 6:18 UTC (permalink / raw)
To: pbs-devel
From: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
---
proxmox-apt-api-types/Cargo.toml | 2 +-
proxmox-apt-api-types/debian/control | 8 ++---
proxmox-apt-api-types/src/lib.rs | 48 ++++------------------------
3 files changed, 12 insertions(+), 46 deletions(-)
diff --git a/proxmox-apt-api-types/Cargo.toml b/proxmox-apt-api-types/Cargo.toml
index e2ab46ad..e6482146 100644
--- a/proxmox-apt-api-types/Cargo.toml
+++ b/proxmox-apt-api-types/Cargo.toml
@@ -9,7 +9,7 @@ exclude.workspace = true
description = "APT API type definitions."
[dependencies]
-anyhow.workspace = true
serde = { workspace = true, features = ["derive"] }
+serde_plain.workspace = true
proxmox-schema = { workspace = true, features = ["api-macro"] }
proxmox-config-digest.workspace = true
diff --git a/proxmox-apt-api-types/debian/control b/proxmox-apt-api-types/debian/control
index 708e8c4b..5b50e114 100644
--- a/proxmox-apt-api-types/debian/control
+++ b/proxmox-apt-api-types/debian/control
@@ -6,12 +6,12 @@ Build-Depends: debhelper (>= 12),
cargo:native <!nocheck>,
rustc:native <!nocheck>,
libstd-rust-dev <!nocheck>,
- librust-anyhow-1+default-dev <!nocheck>,
librust-proxmox-config-digest-0.1+default-dev <!nocheck>,
librust-proxmox-schema-3+api-macro-dev (>= 3.1.1-~~) <!nocheck>,
librust-proxmox-schema-3+default-dev (>= 3.1.1-~~) <!nocheck>,
librust-serde-1+default-dev <!nocheck>,
- librust-serde-1+derive-dev <!nocheck>
+ librust-serde-1+derive-dev <!nocheck>,
+ librust-serde-plain-1+default-dev <!nocheck>
Maintainer: Proxmox Support Team <support@proxmox.com>
Standards-Version: 4.6.2
Vcs-Git: git://git.proxmox.com/git/proxmox-apt.git
@@ -24,12 +24,12 @@ Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
- librust-anyhow-1+default-dev,
librust-proxmox-config-digest-0.1+default-dev,
librust-proxmox-schema-3+api-macro-dev (>= 3.1.1-~~),
librust-proxmox-schema-3+default-dev (>= 3.1.1-~~),
librust-serde-1+default-dev,
- librust-serde-1+derive-dev
+ librust-serde-1+derive-dev,
+ librust-serde-plain-1+default-dev
Provides:
librust-proxmox-apt-api-types+default-dev (= ${binary:Version}),
librust-proxmox-apt-api-types-1-dev (= ${binary:Version}),
diff --git a/proxmox-apt-api-types/src/lib.rs b/proxmox-apt-api-types/src/lib.rs
index 3b6ac9e4..80d5ec4b 100644
--- a/proxmox-apt-api-types/src/lib.rs
+++ b/proxmox-apt-api-types/src/lib.rs
@@ -1,6 +1,5 @@
use std::fmt::Display;
-use anyhow::{bail, Error};
use serde::{Deserialize, Serialize};
use proxmox_config_digest::ConfigDigest;
@@ -16,26 +15,8 @@ pub enum APTRepositoryFileType {
Sources,
}
-impl TryFrom<&str> for APTRepositoryFileType {
- type Error = Error;
-
- fn try_from(file_type: &str) -> Result<Self, Error> {
- match file_type {
- "list" => Ok(APTRepositoryFileType::List),
- "sources" => Ok(APTRepositoryFileType::Sources),
- _ => bail!("invalid file type '{file_type}'"),
- }
- }
-}
-
-impl Display for APTRepositoryFileType {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- APTRepositoryFileType::List => write!(f, "list"),
- APTRepositoryFileType::Sources => write!(f, "sources"),
- }
- }
-}
+serde_plain::derive_display_from_serialize!(APTRepositoryFileType);
+serde_plain::derive_fromstr_from_deserialize!(APTRepositoryFileType);
#[api]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
@@ -47,26 +28,8 @@ pub enum APTRepositoryPackageType {
DebSrc,
}
-impl TryFrom<&str> for APTRepositoryPackageType {
- type Error = Error;
-
- fn try_from(package_type: &str) -> Result<Self, Error> {
- match package_type {
- "deb" => Ok(APTRepositoryPackageType::Deb),
- "deb-src" => Ok(APTRepositoryPackageType::DebSrc),
- _ => bail!("invalid package type '{package_type}'"),
- }
- }
-}
-
-impl Display for APTRepositoryPackageType {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- APTRepositoryPackageType::Deb => write!(f, "deb"),
- APTRepositoryPackageType::DebSrc => write!(f, "deb-src"),
- }
- }
-}
+serde_plain::derive_display_from_serialize!(APTRepositoryPackageType);
+serde_plain::derive_fromstr_from_deserialize!(APTRepositoryPackageType);
#[api(
properties: {
@@ -327,6 +290,9 @@ pub enum APTRepositoryHandle {
CephReefTest,
}
+serde_plain::derive_display_from_serialize!(APTRepositoryHandle);
+serde_plain::derive_fromstr_from_deserialize!(APTRepositoryHandle);
+
#[api()]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
--
2.39.2
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 8+ messages in thread
* [pbs-devel] [PATCH proxmox 3/6] apt: avoid direct impl on api types (use traits instead)
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 ` Wolfgang Bumiller
2024-07-09 6:18 ` [pbs-devel] [PATCH proxmox 4/6] apt: use api types from apt-api-types crate Wolfgang Bumiller
` (2 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Wolfgang Bumiller @ 2024-07-09 6:18 UTC (permalink / raw)
To: pbs-devel
From: Dietmar Maurer <dietmar@proxmox.com>
So that we can use api types from expternal crate proxmox-apt-api-types.
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
---
proxmox-apt/src/repositories/file.rs | 68 ++++++++++++-------
.../src/repositories/file/list_parser.rs | 4 +-
.../src/repositories/file/sources_parser.rs | 1 +
proxmox-apt/src/repositories/mod.rs | 3 +
proxmox-apt/src/repositories/repository.rs | 60 ++++++++++------
proxmox-apt/src/repositories/standard.rs | 38 +++++++----
proxmox-apt/tests/repositories.rs | 3 +
7 files changed, 118 insertions(+), 59 deletions(-)
diff --git a/proxmox-apt/src/repositories/file.rs b/proxmox-apt/src/repositories/file.rs
index 0d21ce3c..086abf49 100644
--- a/proxmox-apt/src/repositories/file.rs
+++ b/proxmox-apt/src/repositories/file.rs
@@ -9,6 +9,8 @@ use crate::repositories::repository::{
APTRepository, APTRepositoryFileType, APTRepositoryPackageType,
};
+use crate::repositories::repository::APTRepositoryImpl;
+
use proxmox_schema::api;
mod list_parser;
@@ -116,13 +118,46 @@ pub struct APTRepositoryInfo {
pub message: String,
}
-impl APTRepositoryFile {
+pub trait APTRepositoryFileImpl {
/// Creates a new `APTRepositoryFile` without parsing.
///
/// If the file is hidden, the path points to a directory, or the extension
/// is usually ignored by APT (e.g. `.orig`), `Ok(None)` is returned, while
/// invalid file names yield an error.
- pub fn new<P: AsRef<Path>>(path: P) -> Result<Option<Self>, APTRepositoryFileError> {
+ fn new<P: AsRef<Path>>(path: P) -> Result<Option<APTRepositoryFile>, APTRepositoryFileError>;
+
+ fn with_content(content: String, content_type: APTRepositoryFileType) -> Self;
+
+ /// Check if the file exists.
+ fn exists(&self) -> bool;
+
+ fn read_with_digest(&self) -> Result<(Vec<u8>, [u8; 32]), APTRepositoryFileError>;
+
+ /// Create an `APTRepositoryFileError`.
+ fn err(&self, error: Error) -> APTRepositoryFileError;
+
+ /// Parses the APT repositories configured in the file on disk, including
+ /// disabled ones.
+ ///
+ /// Resets the current repositories and digest, even on failure.
+ fn parse(&mut self) -> Result<(), APTRepositoryFileError>;
+
+ /// Writes the repositories to the file on disk.
+ ///
+ /// If a digest is provided, checks that the current content of the file still
+ /// produces the same one.
+ fn write(&self) -> Result<(), APTRepositoryFileError>;
+
+ /// Checks if old or unstable suites are configured and that the Debian security repository
+ /// has the correct suite. Also checks that the `stable` keyword is not used.
+ fn check_suites(&self, current_codename: DebianCodename) -> Vec<APTRepositoryInfo>;
+
+ /// Checks for official URIs.
+ fn check_uris(&self) -> Vec<APTRepositoryInfo>;
+}
+
+impl APTRepositoryFileImpl for APTRepositoryFile {
+ fn new<P: AsRef<Path>>(path: P) -> Result<Option<Self>, APTRepositoryFileError> {
let path: PathBuf = path.as_ref().to_path_buf();
let new_err = |path_string: String, err: &str| APTRepositoryFileError {
@@ -197,7 +232,7 @@ impl APTRepositoryFile {
}))
}
- pub fn with_content(content: String, content_type: APTRepositoryFileType) -> Self {
+ fn with_content(content: String, content_type: APTRepositoryFileType) -> Self {
Self {
file_type: content_type,
content: Some(content),
@@ -207,8 +242,7 @@ impl APTRepositoryFile {
}
}
- /// Check if the file exists.
- pub fn exists(&self) -> bool {
+ fn exists(&self) -> bool {
if let Some(path) = &self.path {
PathBuf::from(path).exists()
} else {
@@ -216,7 +250,7 @@ impl APTRepositoryFile {
}
}
- pub fn read_with_digest(&self) -> Result<(Vec<u8>, [u8; 32]), APTRepositoryFileError> {
+ fn read_with_digest(&self) -> Result<(Vec<u8>, [u8; 32]), APTRepositoryFileError> {
if let Some(path) = &self.path {
let content = std::fs::read(path).map_err(|err| self.err(format_err!("{}", err)))?;
let digest = openssl::sha::sha256(&content);
@@ -233,19 +267,14 @@ impl APTRepositoryFile {
}
}
- /// Create an `APTRepositoryFileError`.
- pub fn err(&self, error: Error) -> APTRepositoryFileError {
+ fn err(&self, error: Error) -> APTRepositoryFileError {
APTRepositoryFileError {
path: self.path.clone().unwrap_or_default(),
error: error.to_string(),
}
}
- /// Parses the APT repositories configured in the file on disk, including
- /// disabled ones.
- ///
- /// Resets the current repositories and digest, even on failure.
- pub fn parse(&mut self) -> Result<(), APTRepositoryFileError> {
+ fn parse(&mut self) -> Result<(), APTRepositoryFileError> {
self.repositories.clear();
self.digest = None;
@@ -269,11 +298,7 @@ impl APTRepositoryFile {
Ok(())
}
- /// Writes the repositories to the file on disk.
- ///
- /// If a digest is provided, checks that the current content of the file still
- /// produces the same one.
- pub fn write(&self) -> Result<(), APTRepositoryFileError> {
+ fn write(&self) -> Result<(), APTRepositoryFileError> {
let path = match &self.path {
Some(path) => path,
None => {
@@ -336,9 +361,7 @@ impl APTRepositoryFile {
Ok(())
}
- /// Checks if old or unstable suites are configured and that the Debian security repository
- /// has the correct suite. Also checks that the `stable` keyword is not used.
- pub fn check_suites(&self, current_codename: DebianCodename) -> Vec<APTRepositoryInfo> {
+ fn check_suites(&self, current_codename: DebianCodename) -> Vec<APTRepositoryInfo> {
let mut infos = vec![];
let path = match &self.path {
@@ -427,8 +450,7 @@ impl APTRepositoryFile {
infos
}
- /// Checks for official URIs.
- pub fn check_uris(&self) -> Vec<APTRepositoryInfo> {
+ fn check_uris(&self) -> Vec<APTRepositoryInfo> {
let mut infos = vec![];
let path = match &self.path {
diff --git a/proxmox-apt/src/repositories/file/list_parser.rs b/proxmox-apt/src/repositories/file/list_parser.rs
index 0edeea7f..93bbcc12 100644
--- a/proxmox-apt/src/repositories/file/list_parser.rs
+++ b/proxmox-apt/src/repositories/file/list_parser.rs
@@ -3,9 +3,9 @@ use std::iter::Iterator;
use anyhow::{bail, format_err, Error};
-use crate::repositories::{APTRepository, APTRepositoryFileType, APTRepositoryOption};
-
use super::APTRepositoryParser;
+use crate::repositories::APTRepositoryImpl;
+use crate::repositories::{APTRepository, APTRepositoryFileType, APTRepositoryOption};
// TODO convert %-escape characters. Also adapt printing back accordingly,
// because at least '%' needs to be re-escaped when printing.
diff --git a/proxmox-apt/src/repositories/file/sources_parser.rs b/proxmox-apt/src/repositories/file/sources_parser.rs
index 9424bbe2..213db9d6 100644
--- a/proxmox-apt/src/repositories/file/sources_parser.rs
+++ b/proxmox-apt/src/repositories/file/sources_parser.rs
@@ -3,6 +3,7 @@ use std::iter::Iterator;
use anyhow::{bail, Error};
+use crate::repositories::APTRepositoryImpl;
use crate::repositories::{
APTRepository, APTRepositoryFileType, APTRepositoryOption, APTRepositoryPackageType,
};
diff --git a/proxmox-apt/src/repositories/mod.rs b/proxmox-apt/src/repositories/mod.rs
index 26e4192c..014f1820 100644
--- a/proxmox-apt/src/repositories/mod.rs
+++ b/proxmox-apt/src/repositories/mod.rs
@@ -4,17 +4,20 @@ use std::path::PathBuf;
use anyhow::{bail, Error};
mod repository;
+pub use repository::APTRepositoryImpl;
pub use repository::{
APTRepository, APTRepositoryFileType, APTRepositoryOption, APTRepositoryPackageType,
};
mod file;
+pub use file::APTRepositoryFileImpl;
pub use file::{APTRepositoryFile, APTRepositoryFileError, APTRepositoryInfo};
mod release;
pub use release::{get_current_release_codename, DebianCodename};
mod standard;
+pub use standard::APTRepositoryHandleImpl;
pub use standard::{APTRepositoryHandle, APTStandardRepository};
const APT_SOURCES_LIST_FILENAME: &str = "/etc/apt/sources.list";
diff --git a/proxmox-apt/src/repositories/repository.rs b/proxmox-apt/src/repositories/repository.rs
index 91bd64f2..a07db7cb 100644
--- a/proxmox-apt/src/repositories/repository.rs
+++ b/proxmox-apt/src/repositories/repository.rs
@@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
use proxmox_schema::api;
use crate::repositories::standard::APTRepositoryHandle;
+use crate::repositories::standard::APTRepositoryHandleImpl;
#[api]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
@@ -188,9 +189,41 @@ pub struct APTRepository {
pub enabled: bool,
}
-impl APTRepository {
+pub trait APTRepositoryImpl {
/// Crates an empty repository.
- pub fn new(file_type: APTRepositoryFileType) -> Self {
+ fn new(file_type: APTRepositoryFileType) -> Self;
+
+ /// Changes the `enabled` flag and makes sure the `Enabled` option for
+ /// `APTRepositoryPackageType::Sources` repositories is updated too.
+ fn set_enabled(&mut self, enabled: bool);
+
+ /// Makes sure that all basic properties of a repository are present and not obviously invalid.
+ fn basic_check(&self) -> Result<(), Error>;
+
+ /// Checks if the repository is the one referenced by the handle.
+ fn is_referenced_repository(
+ &self,
+ handle: APTRepositoryHandle,
+ product: &str,
+ suite: &str,
+ ) -> bool;
+
+ /// Guess the origin from the repository's URIs.
+ ///
+ /// Intended to be used as a fallback for get_cached_origin.
+ fn origin_from_uris(&self) -> Option<String>;
+
+ /// Get the `Origin:` value from a cached InRelease file.
+ fn get_cached_origin(&self) -> Result<Option<String>, Error>;
+
+ /// Writes a repository in the corresponding format followed by a blank.
+ ///
+ /// Expects that `basic_check()` for the repository was successful.
+ fn write(&self, w: &mut dyn Write) -> Result<(), Error>;
+}
+
+impl APTRepositoryImpl for APTRepository {
+ fn new(file_type: APTRepositoryFileType) -> Self {
Self {
types: vec![],
uris: vec![],
@@ -203,9 +236,7 @@ impl APTRepository {
}
}
- /// Changes the `enabled` flag and makes sure the `Enabled` option for
- /// `APTRepositoryPackageType::Sources` repositories is updated too.
- pub fn set_enabled(&mut self, enabled: bool) {
+ fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
if self.file_type == APTRepositoryFileType::Sources {
@@ -226,8 +257,7 @@ impl APTRepository {
}
}
- /// Makes sure that all basic properties of a repository are present and not obviously invalid.
- pub fn basic_check(&self) -> Result<(), Error> {
+ fn basic_check(&self) -> Result<(), Error> {
if self.types.is_empty() {
bail!("missing package type(s)");
}
@@ -267,8 +297,7 @@ impl APTRepository {
Ok(())
}
- /// Checks if the repository is the one referenced by the handle.
- pub fn is_referenced_repository(
+ fn is_referenced_repository(
&self,
handle: APTRepositoryHandle,
product: &str,
@@ -299,10 +328,7 @@ impl APTRepository {
&& found_component
}
- /// Guess the origin from the repository's URIs.
- ///
- /// Intended to be used as a fallback for get_cached_origin.
- pub fn origin_from_uris(&self) -> Option<String> {
+ fn origin_from_uris(&self) -> Option<String> {
for uri in self.uris.iter() {
if let Some(host) = host_from_uri(uri) {
if host == "proxmox.com" || host.ends_with(".proxmox.com") {
@@ -318,8 +344,7 @@ impl APTRepository {
None
}
- /// Get the `Origin:` value from a cached InRelease file.
- pub fn get_cached_origin(&self) -> Result<Option<String>, Error> {
+ fn get_cached_origin(&self) -> Result<Option<String>, Error> {
for uri in self.uris.iter() {
for suite in self.suites.iter() {
let mut file = release_filename(uri, suite, false);
@@ -353,10 +378,7 @@ impl APTRepository {
Ok(None)
}
- /// Writes a repository in the corresponding format followed by a blank.
- ///
- /// Expects that `basic_check()` for the repository was successful.
- pub fn write(&self, w: &mut dyn Write) -> Result<(), Error> {
+ fn write(&self, w: &mut dyn Write) -> Result<(), Error> {
match self.file_type {
APTRepositoryFileType::List => write_one_line(self, w),
APTRepositoryFileType::Sources => write_stanza(self, w),
diff --git a/proxmox-apt/src/repositories/standard.rs b/proxmox-apt/src/repositories/standard.rs
index 29cc7885..7858fac4 100644
--- a/proxmox-apt/src/repositories/standard.rs
+++ b/proxmox-apt/src/repositories/standard.rs
@@ -111,9 +111,26 @@ impl Display for APTRepositoryHandle {
}
}
-impl APTRepositoryHandle {
+pub trait APTRepositoryHandleImpl {
/// Get the description for the repository.
- pub fn description(self) -> String {
+ fn description(self) -> String;
+ /// Get the display name of the repository.
+ fn name(self) -> String;
+ /// Get the standard file path for the repository referenced by the handle.
+ fn path(self, product: &str) -> String;
+ /// Get package type, possible URIs and the component associated with the handle.
+ ///
+ /// The first URI is the preferred one.
+ fn info(self, product: &str) -> (APTRepositoryPackageType, Vec<String>, 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).
+ fn to_repository(self, product: &str, suite: &str) -> APTRepository;
+}
+
+impl APTRepositoryHandleImpl for APTRepositoryHandle {
+ fn description(self) -> String {
match self {
APTRepositoryHandle::Enterprise => {
"This is the default, stable, and recommended repository, available for all \
@@ -155,8 +172,7 @@ impl APTRepositoryHandle {
.to_string()
}
- /// Get the display name of the repository.
- pub fn name(self) -> String {
+ fn name(self) -> String {
match self {
APTRepositoryHandle::Enterprise => "Enterprise",
APTRepositoryHandle::NoSubscription => "No-Subscription",
@@ -171,8 +187,7 @@ impl APTRepositoryHandle {
.to_string()
}
- /// Get the standard file path for the repository referenced by the handle.
- pub fn path(self, product: &str) -> String {
+ fn path(self, product: &str) -> String {
match self {
APTRepositoryHandle::Enterprise => {
format!("/etc/apt/sources.list.d/{}-enterprise.list", product)
@@ -188,10 +203,7 @@ impl APTRepositoryHandle {
}
}
- /// Get package type, possible URIs and the component associated with the handle.
- ///
- /// The first URI is the preferred one.
- pub fn info(self, product: &str) -> (APTRepositoryPackageType, Vec<String>, String) {
+ fn info(self, product: &str) -> (APTRepositoryPackageType, Vec<String>, String) {
match self {
APTRepositoryHandle::Enterprise => (
APTRepositoryPackageType::Deb,
@@ -259,11 +271,7 @@ impl APTRepositoryHandle {
}
}
- /// 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 {
+ fn to_repository(self, product: &str, suite: &str) -> APTRepository {
let (package_type, uris, component) = self.info(product);
APTRepository {
diff --git a/proxmox-apt/tests/repositories.rs b/proxmox-apt/tests/repositories.rs
index ae92e51c..37d665bf 100644
--- a/proxmox-apt/tests/repositories.rs
+++ b/proxmox-apt/tests/repositories.rs
@@ -8,6 +8,9 @@ use proxmox_apt::repositories::{
check_repositories, get_current_release_codename, standard_repositories, APTRepositoryFile,
APTRepositoryHandle, APTRepositoryInfo, APTStandardRepository, DebianCodename,
};
+use proxmox_apt::repositories::{
+ APTRepositoryFileImpl, APTRepositoryHandleImpl, APTRepositoryImpl,
+};
fn create_clean_directory(path: &PathBuf) -> Result<(), Error> {
match std::fs::remove_dir_all(path) {
--
2.39.2
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 8+ messages in thread
* [pbs-devel] [PATCH proxmox 4/6] apt: use api types from apt-api-types crate
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 ` Wolfgang Bumiller
2024-07-09 6:18 ` [pbs-devel] [PATCH proxmox 5/6] apt: add cache feature Wolfgang Bumiller
2024-07-09 6:18 ` [pbs-devel] [PATCH proxmox 6/6] apt: avoid global apt config Wolfgang Bumiller
4 siblings, 0 replies; 8+ messages in thread
From: Wolfgang Bumiller @ 2024-07-09 6:18 UTC (permalink / raw)
To: pbs-devel
From: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
---
proxmox-apt/Cargo.toml | 5 +-
proxmox-apt/debian/control | 10 +-
proxmox-apt/src/repositories/file.rs | 119 ++---------
.../src/repositories/file/list_parser.rs | 2 +-
.../src/repositories/file/sources_parser.rs | 2 +-
proxmox-apt/src/repositories/mod.rs | 37 ++--
proxmox-apt/src/repositories/repository.rs | 187 +-----------------
proxmox-apt/src/repositories/standard.rs | 107 +---------
proxmox-apt/tests/repositories.rs | 24 +--
9 files changed, 65 insertions(+), 428 deletions(-)
diff --git a/proxmox-apt/Cargo.toml b/proxmox-apt/Cargo.toml
index 34573543..bbd4ff89 100644
--- a/proxmox-apt/Cargo.toml
+++ b/proxmox-apt/Cargo.toml
@@ -8,7 +8,7 @@ license.workspace = true
repository.workspace = true
homepage.workspace = true
-exclude = [ "debian" ]
+exclude = ["debian"]
[dependencies]
anyhow.workspace = true
@@ -20,4 +20,5 @@ serde_json.workspace = true
rfc822-like = "0.2.1"
-proxmox-schema = { workspace = true, features = [ "api-macro" ] }
+proxmox-apt-api-types.workspace = true
+proxmox-config-digest = { workspace = true, features = ["openssl"] }
diff --git a/proxmox-apt/debian/control b/proxmox-apt/debian/control
index c3248212..347631e6 100644
--- a/proxmox-apt/debian/control
+++ b/proxmox-apt/debian/control
@@ -10,8 +10,9 @@ Build-Depends: debhelper (>= 12),
librust-hex-0.4+default-dev <!nocheck>,
librust-once-cell-1+default-dev (>= 1.3.1-~~) <!nocheck>,
librust-openssl-0.10+default-dev <!nocheck>,
- librust-proxmox-schema-3+api-macro-dev (>= 3.1.1-~~) <!nocheck>,
- librust-proxmox-schema-3+default-dev (>= 3.1.1-~~) <!nocheck>,
+ 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-rfc822-like-0.2+default-dev (>= 0.2.1-~~) <!nocheck>,
librust-serde-1+default-dev <!nocheck>,
librust-serde-1+derive-dev <!nocheck>,
@@ -33,8 +34,9 @@ Depends:
librust-hex-0.4+default-dev,
librust-once-cell-1+default-dev (>= 1.3.1-~~),
librust-openssl-0.10+default-dev,
- librust-proxmox-schema-3+api-macro-dev (>= 3.1.1-~~),
- librust-proxmox-schema-3+default-dev (>= 3.1.1-~~),
+ 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-rfc822-like-0.2+default-dev (>= 0.2.1-~~),
librust-serde-1+default-dev,
librust-serde-1+derive-dev,
diff --git a/proxmox-apt/src/repositories/file.rs b/proxmox-apt/src/repositories/file.rs
index 086abf49..21f612ef 100644
--- a/proxmox-apt/src/repositories/file.rs
+++ b/proxmox-apt/src/repositories/file.rs
@@ -1,123 +1,29 @@
-use std::fmt::Display;
use std::path::{Path, PathBuf};
use anyhow::{format_err, Error};
-use serde::{Deserialize, Serialize};
use crate::repositories::release::DebianCodename;
-use crate::repositories::repository::{
- APTRepository, APTRepositoryFileType, APTRepositoryPackageType,
+use proxmox_apt_api_types::{
+ APTRepository, APTRepositoryFile, APTRepositoryFileError, APTRepositoryFileType,
+ APTRepositoryInfo, APTRepositoryPackageType,
};
use crate::repositories::repository::APTRepositoryImpl;
-use proxmox_schema::api;
-
mod list_parser;
use list_parser::APTListFileParser;
mod sources_parser;
use sources_parser::APTSourcesFileParser;
+use proxmox_config_digest::ConfigDigest;
+
trait APTRepositoryParser {
/// Parse all repositories including the disabled ones and push them onto
/// the provided vector.
fn parse_repositories(&mut self) -> Result<Vec<APTRepository>, Error>;
}
-#[api(
- properties: {
- "file-type": {
- type: APTRepositoryFileType,
- },
- repositories: {
- description: "List of APT repositories.",
- type: Array,
- items: {
- type: APTRepository,
- },
- },
- digest: {
- description: "Digest for the content of the file.",
- optional: true,
- type: Array,
- items: {
- description: "Digest byte.",
- type: u8,
- },
- },
- },
-)]
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "kebab-case")]
-/// Represents an abstract APT repository file.
-pub struct APTRepositoryFile {
- /// The path to the file. If None, `contents` must be set directly.
- #[serde(skip_serializing_if = "Option::is_none")]
- pub path: Option<String>,
-
- /// The type of the file.
- pub file_type: APTRepositoryFileType,
-
- /// List of repositories in the file.
- pub repositories: Vec<APTRepository>,
-
- /// The file content, if already parsed.
- #[serde(skip_serializing_if = "Option::is_none")]
- pub content: Option<String>,
-
- /// Digest of the original contents.
- #[serde(skip_serializing_if = "Option::is_none")]
- pub digest: Option<[u8; 32]>,
-}
-
-#[api]
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "kebab-case")]
-/// Error type for problems with APT repository files.
-pub struct APTRepositoryFileError {
- /// The path to the problematic file.
- pub path: String,
-
- /// The error message.
- pub error: String,
-}
-
-impl Display for APTRepositoryFileError {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "proxmox-apt error for '{}' - {}", self.path, self.error)
- }
-}
-
-impl std::error::Error for APTRepositoryFileError {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- None
- }
-}
-
-#[api]
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
-#[serde(rename_all = "kebab-case")]
-/// Additional information for a repository.
-pub struct APTRepositoryInfo {
- /// Path to the defining file.
- #[serde(default, skip_serializing_if = "String::is_empty")]
- pub path: String,
-
- /// Index of the associated respository within the file (starting from 0).
- pub index: usize,
-
- /// The property from which the info originates (e.g. "Suites")
- #[serde(skip_serializing_if = "Option::is_none")]
- pub property: Option<String>,
-
- /// Info kind (e.g. "warning")
- pub kind: String,
-
- /// Info message
- pub message: String,
-}
-
pub trait APTRepositoryFileImpl {
/// Creates a new `APTRepositoryFile` without parsing.
///
@@ -131,7 +37,7 @@ pub trait APTRepositoryFileImpl {
/// Check if the file exists.
fn exists(&self) -> bool;
- fn read_with_digest(&self) -> Result<(Vec<u8>, [u8; 32]), APTRepositoryFileError>;
+ fn read_with_digest(&self) -> Result<(Vec<u8>, ConfigDigest), APTRepositoryFileError>;
/// Create an `APTRepositoryFileError`.
fn err(&self, error: Error) -> APTRepositoryFileError;
@@ -213,7 +119,8 @@ impl APTRepositoryFileImpl for APTRepositoryFile {
return Ok(None);
}
- let file_type = APTRepositoryFileType::try_from(&extension[..])
+ let file_type = extension[..]
+ .parse()
.map_err(|_| new_err("invalid extension"))?;
if !file_name
@@ -250,15 +157,15 @@ impl APTRepositoryFileImpl for APTRepositoryFile {
}
}
- fn read_with_digest(&self) -> Result<(Vec<u8>, [u8; 32]), APTRepositoryFileError> {
+ fn read_with_digest(&self) -> Result<(Vec<u8>, ConfigDigest), APTRepositoryFileError> {
if let Some(path) = &self.path {
let content = std::fs::read(path).map_err(|err| self.err(format_err!("{}", err)))?;
- let digest = openssl::sha::sha256(&content);
+ let digest = ConfigDigest::from_slice(&content);
Ok((content, digest))
} else if let Some(ref content) = self.content {
let content = content.as_bytes();
- let digest = openssl::sha::sha256(content);
+ let digest = ConfigDigest::from_slice(content);
Ok((content.to_vec(), digest))
} else {
Err(self.err(format_err!(
@@ -308,13 +215,13 @@ impl APTRepositoryFileImpl for APTRepositoryFile {
}
};
- if let Some(digest) = self.digest {
+ if let Some(digest) = &self.digest {
if !self.exists() {
return Err(self.err(format_err!("digest specified, but file does not exist")));
}
let (_, current_digest) = self.read_with_digest()?;
- if digest != current_digest {
+ if digest != ¤t_digest {
return Err(self.err(format_err!("digest mismatch")));
}
}
diff --git a/proxmox-apt/src/repositories/file/list_parser.rs b/proxmox-apt/src/repositories/file/list_parser.rs
index 93bbcc12..8681509a 100644
--- a/proxmox-apt/src/repositories/file/list_parser.rs
+++ b/proxmox-apt/src/repositories/file/list_parser.rs
@@ -184,7 +184,7 @@ impl<R: BufRead> APTListFileParser<R> {
// e.g. quoted "deb" is not accepted by APT, so no need for quote word parsing here
line = match line.split_once(|c| char::is_ascii_whitespace(&c)) {
Some((package_type, rest)) => {
- repo.types.push(package_type.try_into()?);
+ repo.types.push(package_type.parse()?);
rest
}
None => return Ok(None), // empty line
diff --git a/proxmox-apt/src/repositories/file/sources_parser.rs b/proxmox-apt/src/repositories/file/sources_parser.rs
index 213db9d6..017162bb 100644
--- a/proxmox-apt/src/repositories/file/sources_parser.rs
+++ b/proxmox-apt/src/repositories/file/sources_parser.rs
@@ -108,7 +108,7 @@ impl<R: BufRead> APTSourcesFileParser<R> {
}
let mut types = Vec::<APTRepositoryPackageType>::new();
for package_type in values {
- types.push((&package_type[..]).try_into()?);
+ types.push((&package_type[..]).parse()?);
}
repo.types = types;
}
diff --git a/proxmox-apt/src/repositories/mod.rs b/proxmox-apt/src/repositories/mod.rs
index 014f1820..7768a47a 100644
--- a/proxmox-apt/src/repositories/mod.rs
+++ b/proxmox-apt/src/repositories/mod.rs
@@ -4,21 +4,22 @@ use std::path::PathBuf;
use anyhow::{bail, Error};
mod repository;
-pub use repository::APTRepositoryImpl;
-pub use repository::{
- APTRepository, APTRepositoryFileType, APTRepositoryOption, APTRepositoryPackageType,
+use proxmox_apt_api_types::{
+ APTRepository, APTRepositoryFile, APTRepositoryFileError, APTRepositoryFileType,
+ APTRepositoryHandle, APTRepositoryInfo, APTRepositoryOption, APTRepositoryPackageType,
+ APTStandardRepository,
};
+use proxmox_config_digest::ConfigDigest;
+pub use repository::APTRepositoryImpl;
mod file;
pub use file::APTRepositoryFileImpl;
-pub use file::{APTRepositoryFile, APTRepositoryFileError, APTRepositoryInfo};
mod release;
pub use release::{get_current_release_codename, DebianCodename};
mod standard;
-pub use standard::APTRepositoryHandleImpl;
-pub use standard::{APTRepositoryHandle, APTStandardRepository};
+pub use standard::{APTRepositoryHandleImpl, APTStandardRepositoryImpl};
const APT_SOURCES_LIST_FILENAME: &str = "/etc/apt/sources.list";
const APT_SOURCES_LIST_DIRECTORY: &str = "/etc/apt/sources.list.d/";
@@ -28,7 +29,7 @@ const APT_SOURCES_LIST_DIRECTORY: &str = "/etc/apt/sources.list.d/";
/// The digest is invariant with respect to file order.
///
/// Files without a digest are ignored.
-fn common_digest(files: &[APTRepositoryFile]) -> [u8; 32] {
+fn common_digest(files: &[APTRepositoryFile]) -> ConfigDigest {
let mut digests = BTreeMap::new();
for file in files.iter() {
@@ -43,7 +44,7 @@ fn common_digest(files: &[APTRepositoryFile]) -> [u8; 32] {
}
}
- openssl::sha::sha256(&common_raw[..])
+ ConfigDigest::from_slice(&common_raw[..])
}
/// Provides additional information about the repositories.
@@ -86,22 +87,22 @@ pub fn standard_repositories(
suite: DebianCodename,
) -> Vec<APTStandardRepository> {
let mut result = vec![
- APTStandardRepository::from(APTRepositoryHandle::Enterprise),
- APTStandardRepository::from(APTRepositoryHandle::NoSubscription),
- APTStandardRepository::from(APTRepositoryHandle::Test),
+ APTStandardRepository::from_handle(APTRepositoryHandle::Enterprise),
+ APTStandardRepository::from_handle(APTRepositoryHandle::NoSubscription),
+ APTStandardRepository::from_handle(APTRepositoryHandle::Test),
];
if product == "pve" {
result.append(&mut vec![
- APTStandardRepository::from(APTRepositoryHandle::CephQuincyEnterprise),
- APTStandardRepository::from(APTRepositoryHandle::CephQuincyNoSubscription),
- APTStandardRepository::from(APTRepositoryHandle::CephQuincyTest),
+ APTStandardRepository::from_handle(APTRepositoryHandle::CephQuincyEnterprise),
+ APTStandardRepository::from_handle(APTRepositoryHandle::CephQuincyNoSubscription),
+ APTStandardRepository::from_handle(APTRepositoryHandle::CephQuincyTest),
]);
if suite == DebianCodename::Bookworm {
result.append(&mut vec![
- APTStandardRepository::from(APTRepositoryHandle::CephReefEnterprise),
- APTStandardRepository::from(APTRepositoryHandle::CephReefNoSubscription),
- APTStandardRepository::from(APTRepositoryHandle::CephReefTest),
+ APTStandardRepository::from_handle(APTRepositoryHandle::CephReefEnterprise),
+ APTStandardRepository::from_handle(APTRepositoryHandle::CephReefNoSubscription),
+ APTStandardRepository::from_handle(APTRepositoryHandle::CephReefTest),
]);
}
}
@@ -128,7 +129,7 @@ pub fn standard_repositories(
pub type Repositories = (
Vec<APTRepositoryFile>,
Vec<APTRepositoryFileError>,
- [u8; 32],
+ ConfigDigest,
);
/// Returns all APT repositories configured in `/etc/apt/sources.list` and
diff --git a/proxmox-apt/src/repositories/repository.rs b/proxmox-apt/src/repositories/repository.rs
index a07db7cb..596c6385 100644
--- a/proxmox-apt/src/repositories/repository.rs
+++ b/proxmox-apt/src/repositories/repository.rs
@@ -1,193 +1,12 @@
-use std::fmt::Display;
use std::io::{BufRead, BufReader, Write};
use std::path::PathBuf;
use anyhow::{bail, format_err, Error};
-use serde::{Deserialize, Serialize};
-use proxmox_schema::api;
-
-use crate::repositories::standard::APTRepositoryHandle;
use crate::repositories::standard::APTRepositoryHandleImpl;
-
-#[api]
-#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "lowercase")]
-pub enum APTRepositoryFileType {
- /// One-line-style format
- List,
- /// DEB822-style format
- Sources,
-}
-
-impl TryFrom<&str> for APTRepositoryFileType {
- type Error = Error;
-
- fn try_from(file_type: &str) -> Result<Self, Error> {
- match file_type {
- "list" => Ok(APTRepositoryFileType::List),
- "sources" => Ok(APTRepositoryFileType::Sources),
- _ => bail!("invalid file type '{file_type}'"),
- }
- }
-}
-
-impl Display for APTRepositoryFileType {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- APTRepositoryFileType::List => write!(f, "list"),
- APTRepositoryFileType::Sources => write!(f, "sources"),
- }
- }
-}
-
-#[api]
-#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "kebab-case")]
-pub enum APTRepositoryPackageType {
- /// Debian package
- Deb,
- /// Debian source package
- DebSrc,
-}
-
-impl TryFrom<&str> for APTRepositoryPackageType {
- type Error = Error;
-
- fn try_from(package_type: &str) -> Result<Self, Error> {
- match package_type {
- "deb" => Ok(APTRepositoryPackageType::Deb),
- "deb-src" => Ok(APTRepositoryPackageType::DebSrc),
- _ => bail!("invalid package type '{package_type}'"),
- }
- }
-}
-
-impl Display for APTRepositoryPackageType {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- APTRepositoryPackageType::Deb => write!(f, "deb"),
- APTRepositoryPackageType::DebSrc => write!(f, "deb-src"),
- }
- }
-}
-
-#[api(
- properties: {
- Key: {
- description: "Option key.",
- type: String,
- },
- Values: {
- description: "Option values.",
- type: Array,
- items: {
- description: "Value.",
- type: String,
- },
- },
- },
-)]
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "PascalCase")] // for consistency
-/// Additional options for an APT repository.
-/// Used for both single- and mutli-value options.
-pub struct APTRepositoryOption {
- /// Option key.
- pub key: String,
- /// Option value(s).
- pub values: Vec<String>,
-}
-
-#[api(
- properties: {
- Types: {
- description: "List of package types.",
- type: Array,
- items: {
- type: APTRepositoryPackageType,
- },
- },
- URIs: {
- description: "List of repository URIs.",
- type: Array,
- items: {
- description: "Repository URI.",
- type: String,
- },
- },
- Suites: {
- description: "List of distributions.",
- type: Array,
- items: {
- description: "Package distribution.",
- type: String,
- },
- },
- Components: {
- description: "List of repository components.",
- type: Array,
- items: {
- description: "Repository component.",
- type: String,
- },
- },
- Options: {
- type: Array,
- optional: true,
- items: {
- type: APTRepositoryOption,
- },
- },
- Comment: {
- description: "Associated comment.",
- type: String,
- optional: true,
- },
- FileType: {
- type: APTRepositoryFileType,
- },
- Enabled: {
- description: "Whether the repository is enabled or not.",
- type: Boolean,
- },
- },
-)]
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "PascalCase")]
-/// Describes an APT repository.
-pub struct APTRepository {
- /// List of package types.
- #[serde(default, skip_serializing_if = "Vec::is_empty")]
- pub types: Vec<APTRepositoryPackageType>,
-
- /// List of repository URIs.
- #[serde(default, skip_serializing_if = "Vec::is_empty")]
- #[serde(rename = "URIs")]
- pub uris: Vec<String>,
-
- /// List of package distributions.
- #[serde(default, skip_serializing_if = "Vec::is_empty")]
- pub suites: Vec<String>,
-
- /// List of repository components.
- #[serde(default, skip_serializing_if = "Vec::is_empty")]
- pub components: Vec<String>,
-
- /// Additional options.
- #[serde(default, skip_serializing_if = "Vec::is_empty")]
- pub options: Vec<APTRepositoryOption>,
-
- /// Associated comment.
- #[serde(default, skip_serializing_if = "String::is_empty")]
- pub comment: String,
-
- /// Format of the defining file.
- pub file_type: APTRepositoryFileType,
-
- /// Whether the repository is enabled or not.
- pub enabled: bool,
-}
+use proxmox_apt_api_types::{
+ APTRepository, APTRepositoryFileType, APTRepositoryHandle, APTRepositoryOption,
+};
pub trait APTRepositoryImpl {
/// Crates an empty repository.
diff --git a/proxmox-apt/src/repositories/standard.rs b/proxmox-apt/src/repositories/standard.rs
index 7858fac4..64fdea2a 100644
--- a/proxmox-apt/src/repositories/standard.rs
+++ b/proxmox-apt/src/repositories/standard.rs
@@ -1,70 +1,14 @@
-use std::fmt::Display;
-
-use anyhow::{bail, Error};
-use serde::{Deserialize, Serialize};
-
-use crate::repositories::repository::{
- APTRepository, APTRepositoryFileType, APTRepositoryPackageType,
+use proxmox_apt_api_types::{
+ APTRepository, APTRepositoryFileType, APTRepositoryHandle, APTRepositoryPackageType,
+ APTStandardRepository,
};
-use proxmox_schema::api;
-
-#[api(
- properties: {
- handle: {
- description: "Handle referencing a standard repository.",
- type: String,
- },
- },
-)]
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[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, where `None` means
- /// not configured, and `Some(bool)` indicates enabled or disabled.
- #[serde(skip_serializing_if = "Option::is_none")]
- pub status: Option<bool>,
-
- /// Display name of the repository.
- pub name: String,
-
- /// Description of the repository.
- pub description: String,
-}
-
-#[api]
-#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[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 Quincy enterprise repository.
- CephQuincyEnterprise,
- /// Ceph Quincy no-subscription repository.
- CephQuincyNoSubscription,
- /// Ceph Quincy test repository.
- CephQuincyTest,
- // TODO: Add separate enum for ceph releases and use something like
- // `CephTest(CephReleaseCodename),` once the API macro supports it.
- /// Ceph Reef enterprise repository.
- CephReefEnterprise,
- /// Ceph Reef no-subscription repository.
- CephReefNoSubscription,
- /// Ceph Reef test repository.
- CephReefTest,
+pub trait APTStandardRepositoryImpl {
+ fn from_handle(handle: APTRepositoryHandle) -> APTStandardRepository;
}
-impl From<APTRepositoryHandle> for APTStandardRepository {
- fn from(handle: APTRepositoryHandle) -> Self {
+impl APTStandardRepositoryImpl for APTStandardRepository {
+ fn from_handle(handle: APTRepositoryHandle) -> APTStandardRepository {
APTStandardRepository {
handle,
status: None,
@@ -74,43 +18,6 @@ impl From<APTRepositoryHandle> for APTStandardRepository {
}
}
-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-quincy-enterprise" => Ok(APTRepositoryHandle::CephQuincyEnterprise),
- "ceph-quincy-no-subscription" => Ok(APTRepositoryHandle::CephQuincyNoSubscription),
- "ceph-quincy-test" => Ok(APTRepositoryHandle::CephQuincyTest),
- "ceph-reef-enterprise" => Ok(APTRepositoryHandle::CephReefEnterprise),
- "ceph-reef-no-subscription" => Ok(APTRepositoryHandle::CephReefNoSubscription),
- "ceph-reef-test" => Ok(APTRepositoryHandle::CephReefTest),
- _ => 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::CephQuincyEnterprise => write!(f, "ceph-quincy-enterprise"),
- APTRepositoryHandle::CephQuincyNoSubscription => {
- write!(f, "ceph-quincy-no-subscription")
- }
- APTRepositoryHandle::CephQuincyTest => write!(f, "ceph-quincy-test"),
- APTRepositoryHandle::CephReefEnterprise => write!(f, "ceph-reef-enterprise"),
- APTRepositoryHandle::CephReefNoSubscription => write!(f, "ceph-reef-no-subscription"),
- APTRepositoryHandle::CephReefTest => write!(f, "ceph-reef-test"),
- }
- }
-}
-
pub trait APTRepositoryHandleImpl {
/// Get the description for the repository.
fn description(self) -> String;
diff --git a/proxmox-apt/tests/repositories.rs b/proxmox-apt/tests/repositories.rs
index 37d665bf..228ef696 100644
--- a/proxmox-apt/tests/repositories.rs
+++ b/proxmox-apt/tests/repositories.rs
@@ -9,7 +9,7 @@ use proxmox_apt::repositories::{
APTRepositoryHandle, APTRepositoryInfo, APTStandardRepository, DebianCodename,
};
use proxmox_apt::repositories::{
- APTRepositoryFileImpl, APTRepositoryHandleImpl, APTRepositoryImpl,
+ APTRepositoryFileImpl, APTRepositoryHandleImpl, APTRepositoryImpl, APTStandardRepositoryImpl,
};
fn create_clean_directory(path: &PathBuf) -> Result<(), Error> {
@@ -114,7 +114,7 @@ fn test_digest() -> Result<(), Error> {
let new_path = write_dir.join(path.file_name().unwrap());
file.path = Some(new_path.clone().into_os_string().into_string().unwrap());
- let old_digest = file.digest.unwrap();
+ let old_digest = file.digest.clone().unwrap();
// file does not exist yet...
assert!(file.read_with_digest().is_err());
@@ -132,7 +132,7 @@ fn test_digest() -> Result<(), Error> {
repo.enabled = !repo.enabled;
// ...then it should work
- file.digest = Some(old_digest);
+ file.digest = Some(old_digest.clone());
file.write()?;
// expect a different digest, because the repo was modified
@@ -361,15 +361,15 @@ fn test_standard_repositories() -> Result<(), Error> {
let read_dir = test_dir.join("sources.list.d");
let mut expected = vec![
- APTStandardRepository::from(APTRepositoryHandle::Enterprise),
- APTStandardRepository::from(APTRepositoryHandle::NoSubscription),
- APTStandardRepository::from(APTRepositoryHandle::Test),
- APTStandardRepository::from(APTRepositoryHandle::CephQuincyEnterprise),
- APTStandardRepository::from(APTRepositoryHandle::CephQuincyNoSubscription),
- APTStandardRepository::from(APTRepositoryHandle::CephQuincyTest),
- APTStandardRepository::from(APTRepositoryHandle::CephReefEnterprise),
- APTStandardRepository::from(APTRepositoryHandle::CephReefNoSubscription),
- APTStandardRepository::from(APTRepositoryHandle::CephReefTest),
+ APTStandardRepository::from_handle(APTRepositoryHandle::Enterprise),
+ APTStandardRepository::from_handle(APTRepositoryHandle::NoSubscription),
+ APTStandardRepository::from_handle(APTRepositoryHandle::Test),
+ APTStandardRepository::from_handle(APTRepositoryHandle::CephQuincyEnterprise),
+ APTStandardRepository::from_handle(APTRepositoryHandle::CephQuincyNoSubscription),
+ APTStandardRepository::from_handle(APTRepositoryHandle::CephQuincyTest),
+ APTStandardRepository::from_handle(APTRepositoryHandle::CephReefEnterprise),
+ APTStandardRepository::from_handle(APTRepositoryHandle::CephReefNoSubscription),
+ APTStandardRepository::from_handle(APTRepositoryHandle::CephReefTest),
];
let absolute_suite_list = read_dir.join("absolute_suite.list");
--
2.39.2
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 8+ messages in thread
* [pbs-devel] [PATCH proxmox 5/6] apt: add cache feature
2024-07-09 6:18 [pbs-devel] applied-series: [PATCH proxmox 1/6] apt-api-types: new crate Wolfgang Bumiller
` (2 preceding siblings ...)
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
2024-07-09 6:18 ` [pbs-devel] [PATCH proxmox 6/6] apt: avoid global apt config Wolfgang Bumiller
4 siblings, 0 replies; 8+ messages in thread
From: Wolfgang Bumiller @ 2024-07-09 6:18 UTC (permalink / raw)
To: pbs-devel
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
^ permalink raw reply [flat|nested] 8+ messages in thread
* [pbs-devel] [PATCH proxmox 6/6] apt: avoid global apt config
2024-07-09 6:18 [pbs-devel] applied-series: [PATCH proxmox 1/6] apt-api-types: new crate Wolfgang Bumiller
` (3 preceding siblings ...)
2024-07-09 6:18 ` [pbs-devel] [PATCH proxmox 5/6] apt: add cache feature Wolfgang Bumiller
@ 2024-07-09 6:18 ` Wolfgang Bumiller
2024-07-09 10:34 ` Fiona Ebner
4 siblings, 1 reply; 8+ messages in thread
From: Wolfgang Bumiller @ 2024-07-09 6:18 UTC (permalink / raw)
To: pbs-devel
From: Dietmar Maurer <dietmar@proxmox.com>
Because it was only used for the test setup. Instead, we simply
add an apt_lists_dir parameter where we need it.
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
---
proxmox-apt/src/api.rs | 5 +++-
proxmox-apt/src/config.rs | 35 ----------------------
proxmox-apt/src/lib.rs | 2 +-
proxmox-apt/src/repositories/file.rs | 6 ++--
proxmox-apt/src/repositories/mod.rs | 5 ++--
proxmox-apt/src/repositories/repository.rs | 15 +++++-----
proxmox-apt/tests/repositories.rs | 24 +++++----------
7 files changed, 25 insertions(+), 67 deletions(-)
delete mode 100644 proxmox-apt/src/config.rs
diff --git a/proxmox-apt/src/api.rs b/proxmox-apt/src/api.rs
index af01048e..14dfb53b 100644
--- a/proxmox-apt/src/api.rs
+++ b/proxmox-apt/src/api.rs
@@ -1,4 +1,5 @@
// API function that work without feature "cache"
+use std::path::Path;
use anyhow::{bail, Error};
@@ -27,11 +28,13 @@ pub fn get_changelog(options: &APTGetChangelogOptions) -> Result<String, Error>
/// Get APT repository information.
pub fn list_repositories(product: &str) -> Result<APTRepositoriesResult, Error> {
+ let apt_lists_dir = Path::new("/var/lib/apt/lists");
+
let (files, errors, digest) = crate::repositories::repositories()?;
let suite = crate::repositories::get_current_release_codename()?;
- let infos = crate::repositories::check_repositories(&files, suite);
+ let infos = crate::repositories::check_repositories(&files, suite, apt_lists_dir);
let standard_repos = crate::repositories::standard_repositories(&files, product, suite);
Ok(APTRepositoriesResult {
diff --git a/proxmox-apt/src/config.rs b/proxmox-apt/src/config.rs
deleted file mode 100644
index fcb66cbb..00000000
--- a/proxmox-apt/src/config.rs
+++ /dev/null
@@ -1,35 +0,0 @@
-use once_cell::sync::OnceCell;
-
-static GLOBAL_CONFIG: OnceCell<APTConfig> = OnceCell::new();
-
-/// APT configuration variables.
-pub struct APTConfig {
- /// Dir::State
- pub dir_state: String,
- /// Dir::State::Lists
- pub dir_state_lists: String,
-}
-
-impl APTConfig {
- /// Create a new configuration overriding the provided values.
- pub fn new(dir_state: Option<&str>, dir_state_lists: Option<&str>) -> Self {
- Self {
- dir_state: dir_state.unwrap_or("/var/lib/apt/").to_string(),
- dir_state_lists: dir_state_lists.unwrap_or("lists/").to_string(),
- }
- }
-}
-
-/// Get the configuration.
-///
-/// Initializes with default values if init() wasn't called before.
-pub fn get() -> &'static APTConfig {
- GLOBAL_CONFIG.get_or_init(|| APTConfig::new(None, None))
-}
-
-/// Initialize the configuration.
-///
-/// Only has an effect if no init() or get() has been called yet.
-pub fn init(config: APTConfig) -> &'static APTConfig {
- GLOBAL_CONFIG.get_or_init(|| config)
-}
diff --git a/proxmox-apt/src/lib.rs b/proxmox-apt/src/lib.rs
index f25ac90b..cd504739 100644
--- a/proxmox-apt/src/lib.rs
+++ b/proxmox-apt/src/lib.rs
@@ -9,6 +9,6 @@ pub mod 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/src/repositories/file.rs b/proxmox-apt/src/repositories/file.rs
index 21f612ef..078f6407 100644
--- a/proxmox-apt/src/repositories/file.rs
+++ b/proxmox-apt/src/repositories/file.rs
@@ -59,7 +59,7 @@ pub trait APTRepositoryFileImpl {
fn check_suites(&self, current_codename: DebianCodename) -> Vec<APTRepositoryInfo>;
/// Checks for official URIs.
- fn check_uris(&self) -> Vec<APTRepositoryInfo>;
+ fn check_uris(&self, apt_lists_dir: &Path) -> Vec<APTRepositoryInfo>;
}
impl APTRepositoryFileImpl for APTRepositoryFile {
@@ -357,7 +357,7 @@ impl APTRepositoryFileImpl for APTRepositoryFile {
infos
}
- fn check_uris(&self) -> Vec<APTRepositoryInfo> {
+ fn check_uris(&self, apt_lists_dir: &Path) -> Vec<APTRepositoryInfo> {
let mut infos = vec![];
let path = match &self.path {
@@ -366,7 +366,7 @@ impl APTRepositoryFileImpl for APTRepositoryFile {
};
for (n, repo) in self.repositories.iter().enumerate() {
- let mut origin = match repo.get_cached_origin() {
+ let mut origin = match repo.get_cached_origin(apt_lists_dir) {
Ok(option) => option,
Err(_) => None,
};
diff --git a/proxmox-apt/src/repositories/mod.rs b/proxmox-apt/src/repositories/mod.rs
index 7768a47a..451b356b 100644
--- a/proxmox-apt/src/repositories/mod.rs
+++ b/proxmox-apt/src/repositories/mod.rs
@@ -1,5 +1,5 @@
use std::collections::BTreeMap;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
use anyhow::{bail, Error};
@@ -56,12 +56,13 @@ fn common_digest(files: &[APTRepositoryFile]) -> ConfigDigest {
pub fn check_repositories(
files: &[APTRepositoryFile],
current_suite: DebianCodename,
+ apt_lists_dir: &Path,
) -> Vec<APTRepositoryInfo> {
let mut infos = vec![];
for file in files.iter() {
infos.append(&mut file.check_suites(current_suite));
- infos.append(&mut file.check_uris());
+ infos.append(&mut file.check_uris(apt_lists_dir));
}
infos
diff --git a/proxmox-apt/src/repositories/repository.rs b/proxmox-apt/src/repositories/repository.rs
index 596c6385..62ad49d8 100644
--- a/proxmox-apt/src/repositories/repository.rs
+++ b/proxmox-apt/src/repositories/repository.rs
@@ -1,5 +1,5 @@
use std::io::{BufRead, BufReader, Write};
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
use anyhow::{bail, format_err, Error};
@@ -33,7 +33,7 @@ pub trait APTRepositoryImpl {
fn origin_from_uris(&self) -> Option<String>;
/// Get the `Origin:` value from a cached InRelease file.
- fn get_cached_origin(&self) -> Result<Option<String>, Error>;
+ fn get_cached_origin(&self, apt_lists_dir: &Path) -> Result<Option<String>, Error>;
/// Writes a repository in the corresponding format followed by a blank.
///
@@ -163,13 +163,13 @@ impl APTRepositoryImpl for APTRepository {
None
}
- fn get_cached_origin(&self) -> Result<Option<String>, Error> {
+ fn get_cached_origin(&self, apt_lists_dir: &Path) -> Result<Option<String>, Error> {
for uri in self.uris.iter() {
for suite in self.suites.iter() {
- let mut file = release_filename(uri, suite, false);
+ let mut file = release_filename(apt_lists_dir, uri, suite, false);
if !file.exists() {
- file = release_filename(uri, suite, true);
+ file = release_filename(apt_lists_dir, uri, suite, true);
if !file.exists() {
continue;
}
@@ -206,9 +206,8 @@ impl APTRepositoryImpl for APTRepository {
}
/// Get the path to the cached (In)Release file.
-fn release_filename(uri: &str, suite: &str, detached: bool) -> PathBuf {
- let mut path = PathBuf::from(&crate::config::get().dir_state);
- path.push(&crate::config::get().dir_state_lists);
+fn release_filename(apt_lists_dir: &Path, uri: &str, suite: &str, detached: bool) -> PathBuf {
+ let mut path = PathBuf::from(apt_lists_dir);
let encoded_uri = uri_to_filename(uri);
let filename = if detached { "Release" } else { "InRelease" };
diff --git a/proxmox-apt/tests/repositories.rs b/proxmox-apt/tests/repositories.rs
index e4a94525..e4efcab6 100644
--- a/proxmox-apt/tests/repositories.rs
+++ b/proxmox-apt/tests/repositories.rs
@@ -2,8 +2,6 @@ use std::path::PathBuf;
use anyhow::{bail, format_err, Error};
-use proxmox_apt::config::APTConfig;
-
use proxmox_apt::repositories::{
check_repositories, get_current_release_codename, standard_repositories, DebianCodename,
};
@@ -185,17 +183,13 @@ fn test_empty_write() -> Result<(), Error> {
fn test_check_repositories() -> Result<(), Error> {
let test_dir = std::env::current_dir()?.join("tests");
let read_dir = test_dir.join("sources.list.d");
-
- proxmox_apt::config::init(APTConfig::new(
- Some(&test_dir.into_os_string().into_string().unwrap()),
- None,
- ));
+ let apt_lists_dir: PathBuf = test_dir.join("lists");
let absolute_suite_list = read_dir.join("absolute_suite.list");
let mut file = APTRepositoryFile::new(absolute_suite_list)?.unwrap();
file.parse()?;
- let infos = check_repositories(&[file], DebianCodename::Bullseye);
+ let infos = check_repositories(&[file], DebianCodename::Bullseye, &apt_lists_dir);
assert!(infos.is_empty());
let pve_list = read_dir.join("pve.list");
@@ -220,7 +214,7 @@ fn test_check_repositories() -> Result<(), Error> {
}
expected_infos.sort();
- let mut infos = check_repositories(&[file], DebianCodename::Bullseye);
+ let mut infos = check_repositories(&[file], DebianCodename::Bullseye, &apt_lists_dir);
infos.sort();
assert_eq!(infos, expected_infos);
@@ -286,7 +280,7 @@ fn test_check_repositories() -> Result<(), Error> {
}
expected_infos.sort();
- let mut infos = check_repositories(&[file], DebianCodename::Bullseye);
+ let mut infos = check_repositories(&[file], DebianCodename::Bullseye, &apt_lists_dir);
infos.sort();
assert_eq!(infos, expected_infos);
@@ -318,7 +312,7 @@ fn test_check_repositories() -> Result<(), Error> {
}
expected_infos.sort();
- let mut infos = check_repositories(&[file], DebianCodename::Bullseye);
+ let mut infos = check_repositories(&[file], DebianCodename::Bullseye, &apt_lists_dir);
infos.sort();
assert_eq!(infos, expected_infos);
@@ -329,11 +323,7 @@ fn test_check_repositories() -> Result<(), Error> {
fn test_get_cached_origin() -> Result<(), Error> {
let test_dir = std::env::current_dir()?.join("tests");
let read_dir = test_dir.join("sources.list.d");
-
- proxmox_apt::config::init(APTConfig::new(
- Some(&test_dir.into_os_string().into_string().unwrap()),
- None,
- ));
+ let apt_lists_dir: PathBuf = test_dir.clone().join("lists");
let pve_list = read_dir.join("pve.list");
let mut file = APTRepositoryFile::new(pve_list)?.unwrap();
@@ -351,7 +341,7 @@ fn test_get_cached_origin() -> Result<(), Error> {
assert_eq!(file.repositories.len(), origins.len());
for (n, repo) in file.repositories.iter().enumerate() {
- assert_eq!(repo.get_cached_origin()?, origins[n]);
+ assert_eq!(repo.get_cached_origin(&apt_lists_dir)?, origins[n]);
}
Ok(())
--
2.39.2
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [pbs-devel] [PATCH proxmox 6/6] apt: avoid global apt config
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
0 siblings, 1 reply; 8+ messages in thread
From: Fiona Ebner @ 2024-07-09 10:34 UTC (permalink / raw)
To: Proxmox Backup Server development discussion, Wolfgang Bumiller
Am 09.07.24 um 08:18 schrieb Wolfgang Bumiller:
> From: Dietmar Maurer <dietmar@proxmox.com>
>
> Because it was only used for the test setup. Instead, we simply
> add an apt_lists_dir parameter where we need it.
>
It was intended to be built upon and read in the actual values from the
user's apt configuration, i.e. the Dir::State and Dir::State::Lists
configuration variables. Just never got around to adding that feature.
Another option that was planned to be added is the
Dir::Ignore-Files-Silently one for
https://bugzilla.proxmox.com/show_bug.cgi?id=3510
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [pbs-devel] [PATCH proxmox 6/6] apt: avoid global apt config
2024-07-09 10:34 ` Fiona Ebner
@ 2024-07-09 10:48 ` Wolfgang Bumiller
0 siblings, 0 replies; 8+ messages in thread
From: Wolfgang Bumiller @ 2024-07-09 10:48 UTC (permalink / raw)
To: Fiona Ebner; +Cc: Proxmox Backup Server development discussion
On Tue, Jul 09, 2024 at 12:34:37PM GMT, Fiona Ebner wrote:
> Am 09.07.24 um 08:18 schrieb Wolfgang Bumiller:
> > From: Dietmar Maurer <dietmar@proxmox.com>
> >
> > Because it was only used for the test setup. Instead, we simply
> > add an apt_lists_dir parameter where we need it.
> >
>
> It was intended to be built upon and read in the actual values from the
> user's apt configuration, i.e. the Dir::State and Dir::State::Lists
> configuration variables. Just never got around to adding that feature.
> Another option that was planned to be added is the
> Dir::Ignore-Files-Silently one for
> https://bugzilla.proxmox.com/show_bug.cgi?id=3510
If we want more flexibility in there it would probably make sense to
have some kind of top-level type which can be configured and takes care
of passing down the necessary data, with functionality being methods on
that - and get rid of all the freestanding functions and functions
returning longer tuples of not-self-describing data.
Would also make the generated rust docs a lot more natural to read.
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2024-07-09 10:48 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [pbs-devel] [PATCH proxmox 5/6] apt: add cache feature Wolfgang Bumiller
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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox