Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
491814c1cd
@ -33,6 +33,7 @@ members = [
|
||||
"proxmox-rrd-api-types",
|
||||
"proxmox-schema",
|
||||
"proxmox-section-config",
|
||||
"proxmox-sendmail",
|
||||
"proxmox-serde",
|
||||
"proxmox-shared-cache",
|
||||
"proxmox-shared-memory",
|
||||
@ -120,12 +121,12 @@ zstd = { version = "0.12", features = [ "bindgen" ] }
|
||||
# workspace dependencies
|
||||
proxmox-acme = { version = "0.5.3", path = "proxmox-acme", default-features = false }
|
||||
proxmox-api-macro = { version = "1.2.0", path = "proxmox-api-macro" }
|
||||
proxmox-apt-api-types = { version = "1.0.1", path = "proxmox-apt-api-types" }
|
||||
proxmox-apt-api-types = { version = "1.0.2", 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.4", path = "proxmox-compression" }
|
||||
proxmox-daemon = { version = "0.1.0", path = "proxmox-daemon" }
|
||||
proxmox-http = { version = "0.9.2", path = "proxmox-http" }
|
||||
proxmox-http = { version = "0.9.4", path = "proxmox-http" }
|
||||
proxmox-http-error = { version = "0.1.0", path = "proxmox-http-error" }
|
||||
proxmox-human-byte = { version = "0.1.0", path = "proxmox-human-byte" }
|
||||
proxmox-io = { version = "1.1.0", path = "proxmox-io" }
|
||||
@ -138,6 +139,7 @@ proxmox-rest-server = { version = "0.8.0", path = "proxmox-rest-server" }
|
||||
proxmox-router = { version = "3.0.0", path = "proxmox-router" }
|
||||
proxmox-schema = { version = "3.1.2", path = "proxmox-schema" }
|
||||
proxmox-section-config = { version = "2.1.0", path = "proxmox-section-config" }
|
||||
proxmox-sendmail = { version = "0.1.0", path = "proxmox-sendmail" }
|
||||
proxmox-serde = { version = "0.1.1", path = "proxmox-serde", features = [ "serde_json" ] }
|
||||
proxmox-shared-memory = { version = "0.3.0", path = "proxmox-shared-memory" }
|
||||
proxmox-sortable-macro = { version = "0.1.3", path = "proxmox-sortable-macro" }
|
||||
|
2
Makefile
2
Makefile
@ -132,6 +132,8 @@ install-overlay: $(foreach c,$(CRATES), $c-install-overlay)
|
||||
fakeroot $(MAKE) $*-sysext-do
|
||||
%-sysext-do:
|
||||
rm -f extensions/$*.raw
|
||||
rm -rf build/sysext/$*
|
||||
rm -rf build/install/$*
|
||||
$(MAKE) DESTDIR=build/sysext/$* $*-install-overlay
|
||||
mkdir -p extensions
|
||||
mkfs.erofs extensions/$*.raw build/sysext/$*
|
||||
|
56
README.md
56
README.md
@ -95,3 +95,59 @@ Some restrictions apply:
|
||||
- workspace dependency specifications cannot include `optional`
|
||||
- if needed, the `optional` flag needs to be set at the member level when
|
||||
using a workspace dependency
|
||||
|
||||
# Working with *other* projects while changing to *single crates here*
|
||||
|
||||
When crates from this workspace need changes caused by requirements in projects
|
||||
*outside* of this repository, it can often be annoying to keep building and
|
||||
installing `.deb` files.
|
||||
|
||||
Additionally, doing so often requires complete rebuilds as cargo will not pick
|
||||
up *file* changes of external dependencies.
|
||||
|
||||
One way to fix this is by actually changing the version. Since we cut away
|
||||
anything starting at the first hyphen in the version, we need to use a `+`
|
||||
(build metadata) version suffix.
|
||||
|
||||
Eg. turn `5.0.0` into `5.0.0+test8`.
|
||||
|
||||
There are 2 faster ways:
|
||||
|
||||
## Adding a `#[patch.crates-io]` section to the other project.
|
||||
|
||||
Note, however, that this requires *ALL* crates from this workspace to be listed,
|
||||
otherwise multiple conflicting versions of the same crate AND even the same
|
||||
numerical *version* might be built, causing *weird* errors.
|
||||
|
||||
The advantage, however, is that `cargo` will pick up on file changes and rebuild
|
||||
the crate on changes.
|
||||
|
||||
## An in-between: system extensions
|
||||
|
||||
An easy way to quickly get the new package "installed" *temporarily*, such that
|
||||
real apt package upgrades are unaffected is as a system-extension.
|
||||
|
||||
The easiest way — if no other extensions are used — is to just symlink the
|
||||
`extensions/` directory to `/run` as root via:
|
||||
|
||||
```
|
||||
# ln -s ${THIS_DIR}/extensions /run/extensions
|
||||
```
|
||||
|
||||
This does not persist across reboots.
|
||||
(Note: that the `extensions/` directory does not need to exist for the above to
|
||||
work.)
|
||||
|
||||
Once this is done, trying a new version of a crate works by:
|
||||
|
||||
1. Bump the version: eg. `5.0.0+test8` -> `5.0.0+test9`
|
||||
While this is technically optional (the sysext would then *replace*
|
||||
(temporarily) the installed version as long as the sysext is active), just
|
||||
like with `.deb` files, not doing this causes `cargo` to consider the crate
|
||||
to be unchanged and it will not rebuild its code.
|
||||
2. here: `$ make ${crate}-sysext` (rebuilds `extensions/${crate}.raw`)
|
||||
3. as root: `# systemd-sysext refresh` (activates current extensions images)
|
||||
4. in the other project: `$ cargo update && cargo build`
|
||||
|
||||
In the last step, cargo sees that there's a newer version of the crate available
|
||||
and use that.
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-access-control
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -13,7 +13,7 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-proxmox-time-2+default-dev <!nocheck>,
|
||||
librust-serde-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-acme-api
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -17,9 +17,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+derive-dev <!nocheck>,
|
||||
librust-serde-json-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git:
|
||||
Vcs-Browser:
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-acme-api
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-acme
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -14,7 +14,7 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+derive-dev <!nocheck>,
|
||||
librust-serde-json-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git:
|
||||
Vcs-Browser:
|
||||
Homepage: https://proxmox.com
|
||||
@ -69,8 +69,8 @@ Depends:
|
||||
librust-anyhow-1+default-dev,
|
||||
librust-bytes-1+default-dev,
|
||||
librust-hyper-0.14+default-dev (>= 0.14.5-~~),
|
||||
librust-proxmox-http-0.9+client-dev (>= 0.9.2-~~),
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.2-~~)
|
||||
librust-proxmox-http-0.9+client-dev (>= 0.9.4-~~),
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.4-~~)
|
||||
Provides:
|
||||
librust-proxmox-acme-0+async-client-dev (= ${binary:Version}),
|
||||
librust-proxmox-acme-0.5+async-client-dev (= ${binary:Version}),
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-api-macro
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -14,7 +14,7 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-syn-2+full-dev <!nocheck>,
|
||||
librust-syn-2+visit-mut-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "proxmox-apt-api-types"
|
||||
description = "APT API type definitions."
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
@ -1,3 +1,9 @@
|
||||
rust-proxmox-apt-api-types (1.0.2-1) bookworm; urgency=medium
|
||||
|
||||
* add Ceph Squid as valid Proxmox APT repository handle.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Sun, 10 Nov 2024 18:41:28 +0100
|
||||
|
||||
rust-proxmox-apt-api-types (1.0.1-1) bookworm; urgency=medium
|
||||
|
||||
* fix backward compatibility by encoding digest as array
|
||||
|
@ -1,21 +1,22 @@
|
||||
Source: rust-proxmox-apt-api-types
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-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-proxmox-schema-3+api-macro-dev (>= 3.1.2-~~) <!nocheck>,
|
||||
librust-proxmox-schema-3+default-dev (>= 3.1.2-~~) <!nocheck>,
|
||||
librust-serde-1+default-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
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox-apt.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox-apt.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-apt-api-types
|
||||
Rules-Requires-Root: no
|
||||
|
||||
@ -25,8 +26,8 @@ Multi-Arch: same
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
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-proxmox-schema-3+api-macro-dev (>= 3.1.2-~~),
|
||||
librust-proxmox-schema-3+default-dev (>= 3.1.2-~~),
|
||||
librust-serde-1+default-dev,
|
||||
librust-serde-1+derive-dev,
|
||||
librust-serde-plain-1+default-dev
|
||||
@ -36,7 +37,7 @@ Provides:
|
||||
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.1-dev (= ${binary:Version}),
|
||||
librust-proxmox-apt-api-types-1.0.1+default-dev (= ${binary:Version})
|
||||
librust-proxmox-apt-api-types-1.0.2-dev (= ${binary:Version}),
|
||||
librust-proxmox-apt-api-types-1.0.2+default-dev (= ${binary:Version})
|
||||
Description: APT API type definitions - Rust source code
|
||||
Source code for Debianized Rust crate "proxmox-apt-api-types"
|
||||
|
@ -386,6 +386,12 @@ pub enum APTRepositoryHandle {
|
||||
CephReefNoSubscription,
|
||||
/// Ceph Reef test repository.
|
||||
CephReefTest,
|
||||
/// Ceph Squid enterprise repository.
|
||||
CephSquidEnterprise,
|
||||
/// Ceph Squid no-subscription repository.
|
||||
CephSquidNoSubscription,
|
||||
/// Ceph Squid test repository.
|
||||
CephSquidTest,
|
||||
/// Check install
|
||||
#[serde(rename = "checkinstall")]
|
||||
CheckInstall,
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "proxmox-apt"
|
||||
description = "Proxmox library for APT"
|
||||
version = "0.11.3"
|
||||
version = "0.11.5"
|
||||
|
||||
exclude = ["debian"]
|
||||
|
||||
|
@ -1,3 +1,15 @@
|
||||
rust-proxmox-apt (0.11.5-1) bookworm; urgency=medium
|
||||
|
||||
* add Ceph Squid to standard repos for PVE
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 11 Nov 2024 21:08:39 +0100
|
||||
|
||||
rust-proxmox-apt (0.11.4-1) bookworm; urgency=medium
|
||||
|
||||
* add support for Ceph Squid repositories
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Sun, 10 Nov 2024 18:48:37 +0100
|
||||
|
||||
rust-proxmox-apt (0.11.3-1) bookworm; urgency=medium
|
||||
|
||||
* drop unused dependency on once_cell
|
||||
|
@ -1,15 +1,15 @@
|
||||
Source: rust-proxmox-apt
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-anyhow-1+default-dev <!nocheck>,
|
||||
librust-hex-0.4+default-dev <!nocheck>,
|
||||
librust-openssl-0.10+default-dev <!nocheck>,
|
||||
librust-proxmox-apt-api-types-1+default-dev (>= 1.0.1-~~) <!nocheck>,
|
||||
librust-proxmox-apt-api-types-1+default-dev (>= 1.0.2-~~) <!nocheck>,
|
||||
librust-proxmox-config-digest-0.1+default-dev <!nocheck>,
|
||||
librust-proxmox-config-digest-0.1+openssl-dev <!nocheck>,
|
||||
librust-proxmox-sys-0.6+default-dev <!nocheck>,
|
||||
@ -18,7 +18,7 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+derive-dev <!nocheck>,
|
||||
librust-serde-json-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox-apt.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox-apt.git
|
||||
Homepage: https://proxmox.com
|
||||
@ -33,7 +33,7 @@ Depends:
|
||||
librust-anyhow-1+default-dev,
|
||||
librust-hex-0.4+default-dev,
|
||||
librust-openssl-0.10+default-dev,
|
||||
librust-proxmox-apt-api-types-1+default-dev (>= 1.0.1-~~),
|
||||
librust-proxmox-apt-api-types-1+default-dev (>= 1.0.2-~~),
|
||||
librust-proxmox-config-digest-0.1+default-dev,
|
||||
librust-proxmox-config-digest-0.1+openssl-dev,
|
||||
librust-proxmox-sys-0.6+default-dev,
|
||||
@ -49,8 +49,8 @@ Provides:
|
||||
librust-proxmox-apt-0+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-apt-0.11-dev (= ${binary:Version}),
|
||||
librust-proxmox-apt-0.11+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-apt-0.11.3-dev (= ${binary:Version}),
|
||||
librust-proxmox-apt-0.11.3+default-dev (= ${binary:Version})
|
||||
librust-proxmox-apt-0.11.5-dev (= ${binary:Version}),
|
||||
librust-proxmox-apt-0.11.5+default-dev (= ${binary:Version})
|
||||
Description: Proxmox library for APT - Rust source code
|
||||
Source code for Debianized Rust crate "proxmox-apt"
|
||||
|
||||
@ -68,7 +68,7 @@ Depends:
|
||||
Provides:
|
||||
librust-proxmox-apt-0+cache-dev (= ${binary:Version}),
|
||||
librust-proxmox-apt-0.11+cache-dev (= ${binary:Version}),
|
||||
librust-proxmox-apt-0.11.3+cache-dev (= ${binary:Version})
|
||||
librust-proxmox-apt-0.11.5+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.
|
||||
|
@ -127,6 +127,9 @@ pub fn standard_repositories(
|
||||
APTStandardRepository::from_handle(APTRepositoryHandle::CephReefEnterprise),
|
||||
APTStandardRepository::from_handle(APTRepositoryHandle::CephReefNoSubscription),
|
||||
APTStandardRepository::from_handle(APTRepositoryHandle::CephReefTest),
|
||||
APTStandardRepository::from_handle(APTRepositoryHandle::CephSquidEnterprise),
|
||||
APTStandardRepository::from_handle(APTRepositoryHandle::CephSquidNoSubscription),
|
||||
APTStandardRepository::from_handle(APTRepositoryHandle::CephSquidTest),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +75,17 @@ impl APTRepositoryHandleImpl for APTRepositoryHandle {
|
||||
"This repository contains the Ceph Reef packages before they are moved to the \
|
||||
main repository."
|
||||
}
|
||||
APTRepositoryHandle::CephSquidEnterprise => {
|
||||
"This repository holds the production-ready Proxmox Ceph Squid packages."
|
||||
}
|
||||
APTRepositoryHandle::CephSquidNoSubscription => {
|
||||
"This repository holds the Proxmox Ceph Squid packages intended for \
|
||||
non-production use."
|
||||
}
|
||||
APTRepositoryHandle::CephSquidTest => {
|
||||
"This repository contains the Ceph Squid packages before they are moved to the \
|
||||
main repository."
|
||||
}
|
||||
APTRepositoryHandle::CheckInstall => {
|
||||
"The repository contains check install information for binary \
|
||||
executables and libraries."
|
||||
@ -105,6 +116,9 @@ impl APTRepositoryHandleImpl for APTRepositoryHandle {
|
||||
APTRepositoryHandle::CephReefEnterprise => "Ceph Reef Enterprise",
|
||||
APTRepositoryHandle::CephReefNoSubscription => "Ceph Reef No-Subscription",
|
||||
APTRepositoryHandle::CephReefTest => "Ceph Reef Test",
|
||||
APTRepositoryHandle::CephSquidEnterprise => "Ceph Squid Enterprise",
|
||||
APTRepositoryHandle::CephSquidNoSubscription => "Ceph Squid No-Subscription",
|
||||
APTRepositoryHandle::CephSquidTest => "Ceph Squid Test",
|
||||
APTRepositoryHandle::CheckInstall => "checkinstall",
|
||||
APTRepositoryHandle::Classic => "classic",
|
||||
APTRepositoryHandle::DebugInfo => "debuginfo",
|
||||
@ -125,7 +139,10 @@ impl APTRepositoryHandleImpl for APTRepositoryHandle {
|
||||
| APTRepositoryHandle::CephQuincyTest
|
||||
| APTRepositoryHandle::CephReefEnterprise
|
||||
| APTRepositoryHandle::CephReefNoSubscription
|
||||
| APTRepositoryHandle::CephReefTest => "/etc/apt/sources.list.d/ceph.list".to_string(),
|
||||
| APTRepositoryHandle::CephReefTest
|
||||
| APTRepositoryHandle::CephSquidEnterprise
|
||||
| APTRepositoryHandle::CephSquidNoSubscription
|
||||
| APTRepositoryHandle::CephSquidTest => "/etc/apt/sources.list.d/ceph.list".to_string(),
|
||||
APTRepositoryHandle::CheckInstall
|
||||
| APTRepositoryHandle::Classic
|
||||
| APTRepositoryHandle::DebugInfo
|
||||
@ -198,6 +215,21 @@ impl APTRepositoryHandleImpl for APTRepositoryHandle {
|
||||
vec!["http://download.proxmox.com/debian/ceph-reef".to_string()],
|
||||
"test".to_string(),
|
||||
),
|
||||
APTRepositoryHandle::CephSquidEnterprise => (
|
||||
APTRepositoryPackageType::Deb,
|
||||
vec!["https://enterprise.proxmox.com/debian/ceph-squid".to_string()],
|
||||
"enterprise".to_string(),
|
||||
),
|
||||
APTRepositoryHandle::CephSquidNoSubscription => (
|
||||
APTRepositoryPackageType::Deb,
|
||||
vec!["http://download.proxmox.com/debian/ceph-squid".to_string()],
|
||||
"no-subscription".to_string(),
|
||||
),
|
||||
APTRepositoryHandle::CephSquidTest => (
|
||||
APTRepositoryPackageType::Deb,
|
||||
vec!["http://download.proxmox.com/debian/ceph-squid".to_string()],
|
||||
"test".to_string(),
|
||||
),
|
||||
APTRepositoryHandle::CheckInstall => (
|
||||
APTRepositoryPackageType::Rpm,
|
||||
vec!["http://ftp.altlinux.org/pub/distributions/ALTLinux".to_string()],
|
||||
|
@ -661,6 +661,9 @@ fn test_standard_repositories() -> Result<(), Error> {
|
||||
APTStandardRepository::from_handle(APTRepositoryHandle::CephReefEnterprise),
|
||||
APTStandardRepository::from_handle(APTRepositoryHandle::CephReefNoSubscription),
|
||||
APTStandardRepository::from_handle(APTRepositoryHandle::CephReefTest),
|
||||
APTStandardRepository::from_handle(APTRepositoryHandle::CephSquidEnterprise),
|
||||
APTStandardRepository::from_handle(APTRepositoryHandle::CephSquidNoSubscription),
|
||||
APTStandardRepository::from_handle(APTRepositoryHandle::CephSquidTest),
|
||||
];
|
||||
|
||||
let absolute_suite_list = read_dir.join("absolute_suite.list");
|
||||
|
@ -1,10 +1,10 @@
|
||||
Source: rust-proxmox-async
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-anyhow-1+default-dev <!nocheck>,
|
||||
librust-futures-0.3+default-dev <!nocheck>,
|
||||
@ -20,9 +20,10 @@ Build-Depends: debhelper (>= 12),
|
||||
libssl-dev <!nocheck>,
|
||||
uuid-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-async
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,16 +1,17 @@
|
||||
Source: rust-proxmox-auth-api
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-anyhow-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-auth-api
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,15 +1,16 @@
|
||||
Source: rust-proxmox-borrow
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.1
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-borrow
|
||||
Rules-Requires-Root: no
|
||||
|
||||
@ -29,5 +30,4 @@ Provides:
|
||||
librust-proxmox-borrow-1.0.1-dev (= ${binary:Version}),
|
||||
librust-proxmox-borrow-1.0.1+default-dev (= ${binary:Version})
|
||||
Description: Contains the Tied type to tie a value with a lifetime to the value it borrows from - Rust source code
|
||||
This package contains the source for the Rust proxmox-borrow crate, packaged by
|
||||
debcargo for use with cargo and dh-cargo.
|
||||
Source code for Debianized Rust crate "proxmox-borrow"
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-client
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -14,9 +14,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+default-dev <!nocheck>,
|
||||
librust-serde-json-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-client
|
||||
Rules-Requires-Root: no
|
||||
|
||||
@ -55,8 +56,8 @@ Depends:
|
||||
librust-hyper-0.14+default-dev (>= 0.14.5-~~),
|
||||
librust-log-0.4+default-dev (>= 0.4.17-~~),
|
||||
librust-openssl-0.10+default-dev,
|
||||
librust-proxmox-http-0.9+client-dev (>= 0.9.2-~~),
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.2-~~)
|
||||
librust-proxmox-http-0.9+client-dev (>= 0.9.4-~~),
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.4-~~)
|
||||
Provides:
|
||||
librust-proxmox-client-0+hyper-client-dev (= ${binary:Version}),
|
||||
librust-proxmox-client-0.5+hyper-client-dev (= ${binary:Version}),
|
||||
|
@ -223,7 +223,10 @@ impl Client {
|
||||
}
|
||||
.map_err(|err| Error::internal("failed to build request", err))?;
|
||||
|
||||
let response = client.request(request).await.map_err(Error::Anyhow)?;
|
||||
let response = client
|
||||
.request(request)
|
||||
.await
|
||||
.map_err(|err| Error::Client(err.into()))?;
|
||||
|
||||
if response.status() == StatusCode::UNAUTHORIZED {
|
||||
return Err(Error::Unauthorized);
|
||||
@ -318,7 +321,11 @@ impl Client {
|
||||
.body(request.body.into())
|
||||
.map_err(|err| Error::internal("error building login http request", err))?;
|
||||
|
||||
let api_response = self.client.request(request).await.map_err(Error::Anyhow)?;
|
||||
let api_response = self
|
||||
.client
|
||||
.request(request)
|
||||
.await
|
||||
.map_err(|err| Error::Client(err.into()))?;
|
||||
if !api_response.status().is_success() {
|
||||
return Err(Error::api(api_response.status(), "authentication failed"));
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-compression
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -25,9 +25,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-zstd-0.12+bindgen-dev <!nocheck>,
|
||||
librust-zstd-0.12+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-compression
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-config-digest
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -13,9 +13,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+default-dev <!nocheck>,
|
||||
librust-serde-plain-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-config-digest
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-daemon
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -22,9 +22,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-tokio-1+signal-dev (>= 1.6-~~) <!nocheck>,
|
||||
librust-tokio-1+sync-dev (>= 1.6-~~) <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-daemon
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-dns-api
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -16,9 +16,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+default-dev <!nocheck>,
|
||||
librust-serde-1+derive-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-dns-api
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,18 +1,19 @@
|
||||
Source: rust-proxmox-http-error
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-anyhow-1+default-dev <!nocheck>,
|
||||
librust-http-0.2+default-dev <!nocheck>,
|
||||
librust-serde-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.1
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-http-error
|
||||
Rules-Requires-Root: no
|
||||
|
||||
@ -33,5 +34,4 @@ Provides:
|
||||
librust-proxmox-http-error-0.1.0-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-error-0.1.0+default-dev (= ${binary:Version})
|
||||
Description: Proxmox HTTP Error - Rust source code
|
||||
This package contains the source for the Rust proxmox-http-error crate,
|
||||
packaged by debcargo for use with cargo and dh-cargo.
|
||||
Source code for Debianized Rust crate "proxmox-http-error"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "proxmox-http"
|
||||
description = "Proxmox HTTP library"
|
||||
version = "0.9.3"
|
||||
version = "0.9.4"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
@ -1,3 +1,12 @@
|
||||
rust-proxmox-http (0.9.4-1) bookworm; urgency=medium
|
||||
|
||||
* fix #5808: use native-tls instead of rustls for the sync client to avoid
|
||||
problems where a IP is used as a SAN in a certificate.
|
||||
|
||||
* sync client: add HTTP request timeout option.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 26 Nov 2024 14:00:00 +0100
|
||||
|
||||
rust-proxmox-http (0.9.3-1) bookworm; urgency=medium
|
||||
|
||||
* rebuild with proxmox-sys 6.0
|
||||
|
@ -36,8 +36,8 @@ Provides:
|
||||
librust-proxmox-http-0+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9.3-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9.3+default-dev (= ${binary:Version})
|
||||
librust-proxmox-http-0.9.4-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9.4+default-dev (= ${binary:Version})
|
||||
Description: Proxmox HTTP library - Rust source code
|
||||
Source code for Debianized Rust crate "proxmox-http"
|
||||
|
||||
@ -64,7 +64,7 @@ Depends:
|
||||
Provides:
|
||||
librust-proxmox-http-0+client-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9+client-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9.3+client-dev (= ${binary:Version})
|
||||
librust-proxmox-http-0.9.4+client-dev (= ${binary:Version})
|
||||
Description: Proxmox HTTP library - feature "client"
|
||||
This metapackage enables feature "client" for the Rust proxmox-http crate, by
|
||||
pulling in any additional dependencies needed by that feature.
|
||||
@ -83,7 +83,7 @@ Depends:
|
||||
Provides:
|
||||
librust-proxmox-http-0+client-sync-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9+client-sync-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9.3+client-sync-dev (= ${binary:Version})
|
||||
librust-proxmox-http-0.9.4+client-sync-dev (= ${binary:Version})
|
||||
Description: Proxmox HTTP library - feature "client-sync"
|
||||
This metapackage enables feature "client-sync" for the Rust proxmox-http crate,
|
||||
by pulling in any additional dependencies needed by that feature.
|
||||
@ -98,7 +98,7 @@ Depends:
|
||||
Provides:
|
||||
librust-proxmox-http-0+client-trait-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9+client-trait-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9.3+client-trait-dev (= ${binary:Version})
|
||||
librust-proxmox-http-0.9.4+client-trait-dev (= ${binary:Version})
|
||||
Description: Proxmox HTTP library - feature "client-trait"
|
||||
This metapackage enables feature "client-trait" for the Rust proxmox-http
|
||||
crate, by pulling in any additional dependencies needed by that feature.
|
||||
@ -117,7 +117,7 @@ Depends:
|
||||
Provides:
|
||||
librust-proxmox-http-0+http-helpers-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9+http-helpers-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9.3+http-helpers-dev (= ${binary:Version})
|
||||
librust-proxmox-http-0.9.4+http-helpers-dev (= ${binary:Version})
|
||||
Description: Proxmox HTTP library - feature "http-helpers"
|
||||
This metapackage enables feature "http-helpers" for the Rust proxmox-http
|
||||
crate, by pulling in any additional dependencies needed by that feature.
|
||||
@ -132,7 +132,7 @@ Depends:
|
||||
Provides:
|
||||
librust-proxmox-http-0+proxmox-async-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9+proxmox-async-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9.3+proxmox-async-dev (= ${binary:Version})
|
||||
librust-proxmox-http-0.9.4+proxmox-async-dev (= ${binary:Version})
|
||||
Description: Proxmox HTTP library - feature "proxmox-async"
|
||||
This metapackage enables feature "proxmox-async" for the Rust proxmox-http
|
||||
crate, by pulling in any additional dependencies needed by that feature.
|
||||
@ -151,7 +151,7 @@ Depends:
|
||||
Provides:
|
||||
librust-proxmox-http-0+rate-limited-stream-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9+rate-limited-stream-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9.3+rate-limited-stream-dev (= ${binary:Version})
|
||||
librust-proxmox-http-0.9.4+rate-limited-stream-dev (= ${binary:Version})
|
||||
Description: Proxmox HTTP library - feature "rate-limited-stream"
|
||||
This metapackage enables feature "rate-limited-stream" for the Rust proxmox-
|
||||
http crate, by pulling in any additional dependencies needed by that feature.
|
||||
@ -166,7 +166,7 @@ Depends:
|
||||
Provides:
|
||||
librust-proxmox-http-0+rate-limiter-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9+rate-limiter-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9.3+rate-limiter-dev (= ${binary:Version})
|
||||
librust-proxmox-http-0.9.4+rate-limiter-dev (= ${binary:Version})
|
||||
Description: Proxmox HTTP library - feature "rate-limiter"
|
||||
This metapackage enables feature "rate-limiter" for the Rust proxmox-http
|
||||
crate, by pulling in any additional dependencies needed by that feature.
|
||||
@ -191,7 +191,7 @@ Depends:
|
||||
Provides:
|
||||
librust-proxmox-http-0+websocket-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9+websocket-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9.3+websocket-dev (= ${binary:Version})
|
||||
librust-proxmox-http-0.9.4+websocket-dev (= ${binary:Version})
|
||||
Description: Proxmox HTTP library - feature "websocket"
|
||||
This metapackage enables feature "websocket" for the Rust proxmox-http crate,
|
||||
by pulling in any additional dependencies needed by that feature.
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Error;
|
||||
use http::Response;
|
||||
@ -12,16 +13,31 @@ use crate::HttpOptions;
|
||||
/// Blocking HTTP client for usage with [`HttpClient`].
|
||||
pub struct Client {
|
||||
options: HttpOptions,
|
||||
timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(options: HttpOptions) -> Self {
|
||||
Self { options }
|
||||
Self {
|
||||
options,
|
||||
timeout: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_timeout(options: HttpOptions, timeout: Duration) -> Self {
|
||||
Self {
|
||||
options,
|
||||
timeout: Some(timeout),
|
||||
}
|
||||
}
|
||||
|
||||
fn agent(&self) -> Result<ureq::Agent, Error> {
|
||||
let mut builder = ureq::AgentBuilder::new();
|
||||
|
||||
if let Some(timeout) = self.timeout {
|
||||
builder = builder.timeout(timeout);
|
||||
};
|
||||
|
||||
let connector = Arc::new(native_tls::TlsConnector::new()?);
|
||||
builder = builder.tls_connector(connector);
|
||||
|
||||
|
@ -13,9 +13,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-proxmox-serde-0.1+serde-json-dev (>= 0.1.1-~~) <!nocheck>,
|
||||
librust-serde-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-human-byte
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,17 +1,18 @@
|
||||
Source: rust-proxmox-io
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-endian-trait-0.6+arrays-dev <!nocheck>,
|
||||
librust-endian-trait-0.6+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-io
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,15 +1,16 @@
|
||||
Source: rust-proxmox-lang
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-lang
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-ldap
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -12,9 +12,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+default-dev <!nocheck>,
|
||||
librust-serde-1+derive-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-ldap
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "proxmox-log"
|
||||
description = "Logging infrastructure for proxmox"
|
||||
version = "0.2.5"
|
||||
version = "0.2.7"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
@ -1,3 +1,16 @@
|
||||
rust-proxmox-log (0.2.7-1) bookworm; urgency=medium
|
||||
|
||||
* ignore to_stdout parameter, this is now handled by tracing.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 27 Nov 2024 13:29:50 +0100
|
||||
|
||||
rust-proxmox-log (0.2.6-1) bookworm; urgency=medium
|
||||
|
||||
* log: only print error level to syslog/stderr to avoid that, e.g., the
|
||||
whole worker task logs are mirrored to the journal.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 19 Nov 2024 11:32:00 +0100
|
||||
|
||||
rust-proxmox-log (0.2.5-1) bookworm; urgency=medium
|
||||
|
||||
* init_logger: fall back to printing to stderr when syslog is unavailable
|
||||
|
@ -1,10 +1,10 @@
|
||||
Source: rust-proxmox-log
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-anyhow-1+default-dev <!nocheck>,
|
||||
librust-nix-0.26+default-dev (>= 0.26.1-~~) <!nocheck>,
|
||||
@ -17,9 +17,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-tracing-log-0.2+std-dev <!nocheck>,
|
||||
librust-tracing-subscriber-0.3+default-dev (>= 0.3.16-~~) <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-log
|
||||
Rules-Requires-Root: no
|
||||
|
||||
@ -44,7 +45,7 @@ Provides:
|
||||
librust-proxmox-log-0+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-log-0.2-dev (= ${binary:Version}),
|
||||
librust-proxmox-log-0.2+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-log-0.2.5-dev (= ${binary:Version}),
|
||||
librust-proxmox-log-0.2.5+default-dev (= ${binary:Version})
|
||||
librust-proxmox-log-0.2.7-dev (= ${binary:Version}),
|
||||
librust-proxmox-log-0.2.7+default-dev (= ${binary:Version})
|
||||
Description: Logging infrastructure for proxmox - Rust source code
|
||||
Source code for Debianized Rust crate "proxmox-log"
|
||||
|
@ -17,7 +17,8 @@ pub struct FileLogOptions {
|
||||
pub read: bool,
|
||||
/// If set, ensure that the file is newly created or error out if already existing.
|
||||
pub exclusive: bool,
|
||||
/// Duplicate logged messages to STDOUT, like tee
|
||||
/// Duplicate logged messages to STDOUT, like tee.
|
||||
/// NOTE: this is now handled by tracing, this option will be removed soon.
|
||||
pub to_stdout: bool,
|
||||
/// Prefix messages logged to the file with the current local time as RFC 3339
|
||||
pub prefix_time: bool,
|
||||
@ -103,11 +104,12 @@ impl FileLogger {
|
||||
pub fn log<S: AsRef<str>>(&mut self, msg: S) {
|
||||
let msg = msg.as_ref();
|
||||
|
||||
if self.options.to_stdout {
|
||||
let mut stdout = std::io::stdout();
|
||||
let _ = stdout.write_all(msg.as_bytes());
|
||||
let _ = stdout.write_all(b"\n");
|
||||
}
|
||||
// TODO: remove whole to_stdout option, handled by tracing now
|
||||
//if self.options.to_stdout {
|
||||
// let mut stdout = std::io::stdout();
|
||||
// let _ = stdout.write_all(msg.as_bytes());
|
||||
// let _ = stdout.write_all(b"\n");
|
||||
//}
|
||||
|
||||
let line = if self.options.prefix_time {
|
||||
let now = proxmox_time::epoch_i64();
|
||||
@ -128,16 +130,18 @@ impl FileLogger {
|
||||
|
||||
impl std::io::Write for FileLogger {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
|
||||
if self.options.to_stdout {
|
||||
let _ = std::io::stdout().write(buf);
|
||||
}
|
||||
// TODO: remove whole to_stdout option, handled by tracing now
|
||||
//if self.options.to_stdout {
|
||||
// let _ = std::io::stdout().write(buf);
|
||||
//}
|
||||
self.file.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), std::io::Error> {
|
||||
if self.options.to_stdout {
|
||||
let _ = std::io::stdout().flush();
|
||||
}
|
||||
// TODO: remove whole to_stdout option, handled by tracing now
|
||||
//if self.options.to_stdout {
|
||||
// let _ = std::io::stdout().flush();
|
||||
//}
|
||||
self.file.flush()
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-login
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -13,7 +13,7 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+derive-dev <!nocheck>,
|
||||
librust-serde-json-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: https://salsa.debian.org/rust-team/debcargo-conf.git [src/proxmox-login]
|
||||
Vcs-Browser: https://salsa.debian.org/rust-team/debcargo-conf/tree/master/src/proxmox-login
|
||||
Homepage: https://proxmox.com
|
||||
|
@ -1,10 +1,10 @@
|
||||
Source: rust-proxmox-metrics
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-anyhow-1+default-dev <!nocheck>,
|
||||
librust-futures-0.3+default-dev <!nocheck>,
|
||||
@ -12,8 +12,8 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-hyper-0.14+default-dev (>= 0.14.5-~~) <!nocheck>,
|
||||
librust-openssl-0.10+default-dev <!nocheck>,
|
||||
librust-proxmox-async-0.4+default-dev (>= 0.4.1-~~) <!nocheck>,
|
||||
librust-proxmox-http-0.9+client-dev <!nocheck>,
|
||||
librust-proxmox-http-0.9+default-dev <!nocheck>,
|
||||
librust-proxmox-http-0.9+client-dev (>= 0.9.4-~~) <!nocheck>,
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.4-~~) <!nocheck>,
|
||||
librust-serde-1+default-dev <!nocheck>,
|
||||
librust-serde-json-1+default-dev <!nocheck>,
|
||||
librust-tokio-1+default-dev (>= 1.6-~~) <!nocheck>,
|
||||
@ -21,9 +21,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-tokio-1+sync-dev (>= 1.6-~~) <!nocheck>,
|
||||
librust-url-2+default-dev (>= 2.2-~~) <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-metrics
|
||||
Rules-Requires-Root: no
|
||||
|
||||
@ -38,8 +39,8 @@ Depends:
|
||||
librust-hyper-0.14+default-dev (>= 0.14.5-~~),
|
||||
librust-openssl-0.10+default-dev,
|
||||
librust-proxmox-async-0.4+default-dev (>= 0.4.1-~~),
|
||||
librust-proxmox-http-0.9+client-dev,
|
||||
librust-proxmox-http-0.9+default-dev,
|
||||
librust-proxmox-http-0.9+client-dev (>= 0.9.4-~~),
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.4-~~),
|
||||
librust-serde-1+default-dev,
|
||||
librust-serde-json-1+default-dev,
|
||||
librust-tokio-1+default-dev (>= 1.6-~~),
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-network-api
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -15,9 +15,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+default-dev <!nocheck>,
|
||||
librust-serde-1+derive-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-network-api
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "proxmox-notify"
|
||||
description = "implementation of notification base and plugins"
|
||||
version = "0.4.2"
|
||||
version = "0.5.1"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
@ -13,13 +13,15 @@ rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
base64.workspace = true
|
||||
base64 = { workspace = true, optional = true }
|
||||
const_format.workspace = true
|
||||
handlebars = { workspace = true }
|
||||
http = { workspace = true, optional = true }
|
||||
lettre = { workspace = true, optional = true }
|
||||
log.workspace = true
|
||||
mail-parser = { workspace = true, optional = true }
|
||||
openssl.workspace = true
|
||||
percent-encoding = { workspace = true, optional = true }
|
||||
regex.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
@ -30,15 +32,17 @@ proxmox-human-byte.workspace = true
|
||||
proxmox-schema = { workspace = true, features = ["api-macro", "api-types"] }
|
||||
proxmox-section-config = { workspace = true }
|
||||
proxmox-serde.workspace = true
|
||||
proxmox-sendmail = { workspace = true, optional = true }
|
||||
proxmox-sys = { workspace = true, optional = true }
|
||||
proxmox-time.workspace = true
|
||||
proxmox-uuid = { workspace = true, features = ["serde"] }
|
||||
|
||||
[features]
|
||||
default = ["sendmail", "gotify", "smtp"]
|
||||
mail-forwarder = ["dep:mail-parser", "dep:proxmox-sys"]
|
||||
sendmail = ["dep:proxmox-sys"]
|
||||
default = ["sendmail", "gotify", "smtp", "webhook"]
|
||||
mail-forwarder = ["dep:mail-parser", "dep:proxmox-sys", "proxmox-sendmail/mail-forwarder"]
|
||||
sendmail = ["dep:proxmox-sys", "dep:base64", "dep:proxmox-sendmail"]
|
||||
gotify = ["dep:proxmox-http"]
|
||||
pve-context = ["dep:proxmox-sys"]
|
||||
pbs-context = ["dep:proxmox-sys"]
|
||||
smtp = ["dep:lettre"]
|
||||
webhook = ["dep:base64", "dep:http", "dep:percent-encoding", "dep:proxmox-http"]
|
||||
|
@ -1,3 +1,23 @@
|
||||
rust-proxmox-notify (0.5.1-1) bookworm; urgency=medium
|
||||
|
||||
* sendmail: various cleanups and refactoring
|
||||
|
||||
* sendmail: always send multi-part message to improve rendering in certain
|
||||
mail clients.
|
||||
|
||||
* remove irritating 'html template not found' log message for now.
|
||||
|
||||
* gotify, webhooks: timeout requests after 10 seconds to avoid blocking any
|
||||
API thread/worker-process for too long.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 26 Nov 2024 14:19:09 +0100
|
||||
|
||||
rust-proxmox-notify (0.5.0-1) bookworm; urgency=medium
|
||||
|
||||
* implement webhook targets and api to manage them
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Sun, 10 Nov 2024 18:57:36 +0100
|
||||
|
||||
rust-proxmox-notify (0.4.2-1) bookworm; urgency=medium
|
||||
|
||||
* upgrade to proxmox-sys 6.0
|
||||
|
@ -1,26 +1,28 @@
|
||||
Source: rust-proxmox-notify
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-anyhow-1+default-dev <!nocheck>,
|
||||
librust-base64-0.13+default-dev <!nocheck>,
|
||||
librust-const-format-0.2+default-dev <!nocheck>,
|
||||
librust-handlebars-3+default-dev <!nocheck>,
|
||||
librust-http-0.2+default-dev <!nocheck>,
|
||||
librust-lettre-0.11+default-dev (>= 0.11.1-~~) <!nocheck>,
|
||||
librust-log-0.4+default-dev (>= 0.4.17-~~) <!nocheck>,
|
||||
librust-openssl-0.10+default-dev <!nocheck>,
|
||||
librust-proxmox-http-0.9+client-sync-dev (>= 0.9.2-~~) <!nocheck>,
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.2-~~) <!nocheck>,
|
||||
librust-percent-encoding-2+default-dev (>= 2.1-~~) <!nocheck>,
|
||||
librust-proxmox-http-0.9+client-sync-dev (>= 0.9.4-~~) <!nocheck>,
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.4-~~) <!nocheck>,
|
||||
librust-proxmox-http-error-0.1+default-dev <!nocheck>,
|
||||
librust-proxmox-human-byte-0.1+default-dev <!nocheck>,
|
||||
librust-proxmox-schema-3+api-macro-dev (>= 3.1.1-~~) <!nocheck>,
|
||||
librust-proxmox-schema-3+api-types-dev (>= 3.1.1-~~) <!nocheck>,
|
||||
librust-proxmox-schema-3+default-dev (>= 3.1.1-~~) <!nocheck>,
|
||||
librust-proxmox-section-config-2+default-dev <!nocheck>,
|
||||
librust-proxmox-schema-3+api-macro-dev (>= 3.1.2-~~) <!nocheck>,
|
||||
librust-proxmox-schema-3+api-types-dev (>= 3.1.2-~~) <!nocheck>,
|
||||
librust-proxmox-schema-3+default-dev (>= 3.1.2-~~) <!nocheck>,
|
||||
librust-proxmox-section-config-2+default-dev (>= 2.1.0-~~) <!nocheck>,
|
||||
librust-proxmox-serde-0.1+default-dev (>= 0.1.1-~~) <!nocheck>,
|
||||
librust-proxmox-serde-0.1+serde-json-dev (>= 0.1.1-~~) <!nocheck>,
|
||||
librust-proxmox-sys-0.6+default-dev <!nocheck>,
|
||||
@ -32,9 +34,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+derive-dev <!nocheck>,
|
||||
librust-serde-json-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-notify
|
||||
Rules-Requires-Root: no
|
||||
|
||||
@ -44,17 +47,16 @@ Multi-Arch: same
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
librust-anyhow-1+default-dev,
|
||||
librust-base64-0.13+default-dev,
|
||||
librust-const-format-0.2+default-dev,
|
||||
librust-handlebars-3+default-dev,
|
||||
librust-log-0.4+default-dev (>= 0.4.17-~~),
|
||||
librust-openssl-0.10+default-dev,
|
||||
librust-proxmox-http-error-0.1+default-dev,
|
||||
librust-proxmox-human-byte-0.1+default-dev,
|
||||
librust-proxmox-schema-3+api-macro-dev (>= 3.1.1-~~),
|
||||
librust-proxmox-schema-3+api-types-dev (>= 3.1.1-~~),
|
||||
librust-proxmox-schema-3+default-dev (>= 3.1.1-~~),
|
||||
librust-proxmox-section-config-2+default-dev,
|
||||
librust-proxmox-schema-3+api-macro-dev (>= 3.1.2-~~),
|
||||
librust-proxmox-schema-3+api-types-dev (>= 3.1.2-~~),
|
||||
librust-proxmox-schema-3+default-dev (>= 3.1.2-~~),
|
||||
librust-proxmox-section-config-2+default-dev (>= 2.1.0-~~),
|
||||
librust-proxmox-serde-0.1+default-dev (>= 0.1.1-~~),
|
||||
librust-proxmox-serde-0.1+serde-json-dev (>= 0.1.1-~~),
|
||||
librust-proxmox-time-2+default-dev,
|
||||
@ -70,12 +72,14 @@ Suggests:
|
||||
librust-proxmox-notify+gotify-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify+mail-forwarder-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify+pbs-context-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify+smtp-dev (= ${binary:Version})
|
||||
librust-proxmox-notify+sendmail-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify+smtp-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify+webhook-dev (= ${binary:Version})
|
||||
Provides:
|
||||
librust-proxmox-notify-0-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.4-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.4.2-dev (= ${binary:Version})
|
||||
Description: Rust crate "proxmox-notify" - Rust source code
|
||||
librust-proxmox-notify-0.5-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.5.1-dev (= ${binary:Version})
|
||||
Description: Notification base and plugins - Rust source code
|
||||
Source code for Debianized Rust crate "proxmox-notify"
|
||||
|
||||
Package: librust-proxmox-notify+default-dev
|
||||
@ -86,12 +90,13 @@ Depends:
|
||||
librust-proxmox-notify-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify+sendmail-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify+gotify-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify+smtp-dev (= ${binary:Version})
|
||||
librust-proxmox-notify+smtp-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify+webhook-dev (= ${binary:Version})
|
||||
Provides:
|
||||
librust-proxmox-notify-0+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.4+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.4.2+default-dev (= ${binary:Version})
|
||||
Description: Rust crate "proxmox-notify" - feature "default"
|
||||
librust-proxmox-notify-0.5+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.5.1+default-dev (= ${binary:Version})
|
||||
Description: Notification base and plugins - feature "default"
|
||||
This metapackage enables feature "default" for the Rust proxmox-notify crate,
|
||||
by pulling in any additional dependencies needed by that feature.
|
||||
|
||||
@ -101,13 +106,13 @@ Multi-Arch: same
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
librust-proxmox-notify-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9+client-sync-dev (>= 0.9.2-~~),
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.2-~~)
|
||||
librust-proxmox-http-0.9+client-sync-dev (>= 0.9.4-~~),
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.4-~~)
|
||||
Provides:
|
||||
librust-proxmox-notify-0+gotify-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.4+gotify-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.4.2+gotify-dev (= ${binary:Version})
|
||||
Description: Rust crate "proxmox-notify" - feature "gotify"
|
||||
librust-proxmox-notify-0.5+gotify-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.5.1+gotify-dev (= ${binary:Version})
|
||||
Description: Notification base and plugins - feature "gotify"
|
||||
This metapackage enables feature "gotify" for the Rust proxmox-notify crate, by
|
||||
pulling in any additional dependencies needed by that feature.
|
||||
|
||||
@ -121,9 +126,9 @@ Depends:
|
||||
librust-proxmox-sys-0.6+default-dev
|
||||
Provides:
|
||||
librust-proxmox-notify-0+mail-forwarder-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.4+mail-forwarder-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.4.2+mail-forwarder-dev (= ${binary:Version})
|
||||
Description: Rust crate "proxmox-notify" - feature "mail-forwarder"
|
||||
librust-proxmox-notify-0.5+mail-forwarder-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.5.1+mail-forwarder-dev (= ${binary:Version})
|
||||
Description: Notification base and plugins - feature "mail-forwarder"
|
||||
This metapackage enables feature "mail-forwarder" for the Rust proxmox-notify
|
||||
crate, by pulling in any additional dependencies needed by that feature.
|
||||
|
||||
@ -136,22 +141,33 @@ Depends:
|
||||
librust-proxmox-sys-0.6+default-dev
|
||||
Provides:
|
||||
librust-proxmox-notify+pve-context-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify+sendmail-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0+pbs-context-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0+pve-context-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0+sendmail-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.4+pbs-context-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.4+pve-context-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.4+sendmail-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.4.2+pbs-context-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.4.2+pve-context-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.4.2+sendmail-dev (= ${binary:Version})
|
||||
Description: Rust crate "proxmox-notify" - feature "pbs-context" and 2 more
|
||||
librust-proxmox-notify-0.5+pbs-context-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.5+pve-context-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.5.1+pbs-context-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.5.1+pve-context-dev (= ${binary:Version})
|
||||
Description: Notification base and plugins - feature "pbs-context" and 1 more
|
||||
This metapackage enables feature "pbs-context" for the Rust proxmox-notify
|
||||
crate, by pulling in any additional dependencies needed by that feature.
|
||||
.
|
||||
Additionally, this package also provides the "pve-context", and "sendmail"
|
||||
features.
|
||||
Additionally, this package also provides the "pve-context" feature.
|
||||
|
||||
Package: librust-proxmox-notify+sendmail-dev
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
librust-proxmox-notify-dev (= ${binary:Version}),
|
||||
librust-base64-0.13+default-dev,
|
||||
librust-proxmox-sys-0.6+default-dev
|
||||
Provides:
|
||||
librust-proxmox-notify-0+sendmail-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.5+sendmail-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.5.1+sendmail-dev (= ${binary:Version})
|
||||
Description: Notification base and plugins - feature "sendmail"
|
||||
This metapackage enables feature "sendmail" for the Rust proxmox-notify crate,
|
||||
by pulling in any additional dependencies needed by that feature.
|
||||
|
||||
Package: librust-proxmox-notify+smtp-dev
|
||||
Architecture: any
|
||||
@ -162,8 +178,27 @@ Depends:
|
||||
librust-lettre-0.11+default-dev (>= 0.11.1-~~)
|
||||
Provides:
|
||||
librust-proxmox-notify-0+smtp-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.4+smtp-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.4.2+smtp-dev (= ${binary:Version})
|
||||
Description: Rust crate "proxmox-notify" - feature "smtp"
|
||||
librust-proxmox-notify-0.5+smtp-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.5.1+smtp-dev (= ${binary:Version})
|
||||
Description: Notification base and plugins - feature "smtp"
|
||||
This metapackage enables feature "smtp" for the Rust proxmox-notify crate, by
|
||||
pulling in any additional dependencies needed by that feature.
|
||||
|
||||
Package: librust-proxmox-notify+webhook-dev
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
librust-proxmox-notify-dev (= ${binary:Version}),
|
||||
librust-base64-0.13+default-dev,
|
||||
librust-http-0.2+default-dev,
|
||||
librust-percent-encoding-2+default-dev (>= 2.1-~~),
|
||||
librust-proxmox-http-0.9+client-sync-dev (>= 0.9.4-~~),
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.4-~~)
|
||||
Provides:
|
||||
librust-proxmox-notify-0+webhook-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.5+webhook-dev (= ${binary:Version}),
|
||||
librust-proxmox-notify-0.5.1+webhook-dev (= ${binary:Version})
|
||||
Description: Notification base and plugins - feature "webhook"
|
||||
This metapackage enables feature "webhook" for the Rust proxmox-notify crate,
|
||||
by pulling in any additional dependencies needed by that feature.
|
||||
|
@ -15,6 +15,8 @@ pub mod matcher;
|
||||
pub mod sendmail;
|
||||
#[cfg(feature = "smtp")]
|
||||
pub mod smtp;
|
||||
#[cfg(feature = "webhook")]
|
||||
pub mod webhook;
|
||||
|
||||
// We have our own, local versions of http_err and http_bail, because
|
||||
// we don't want to wrap the error in anyhow::Error. If we were to do that,
|
||||
@ -54,6 +56,9 @@ pub enum EndpointType {
|
||||
/// Gotify endpoint
|
||||
#[cfg(feature = "gotify")]
|
||||
Gotify,
|
||||
/// Webhook endpoint
|
||||
#[cfg(feature = "webhook")]
|
||||
Webhook,
|
||||
}
|
||||
|
||||
#[api]
|
||||
@ -113,6 +118,17 @@ pub fn get_targets(config: &Config) -> Result<Vec<Target>, HttpError> {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "webhook")]
|
||||
for endpoint in webhook::get_endpoints(config)? {
|
||||
targets.push(Target {
|
||||
name: endpoint.name,
|
||||
origin: endpoint.origin.unwrap_or(Origin::UserCreated),
|
||||
endpoint_type: EndpointType::Webhook,
|
||||
disable: endpoint.disable,
|
||||
comment: endpoint.comment,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(targets)
|
||||
}
|
||||
|
||||
@ -145,6 +161,10 @@ fn ensure_endpoint_exists(#[allow(unused)] config: &Config, name: &str) -> Resul
|
||||
{
|
||||
exists = exists || smtp::get_endpoint(config, name).is_ok();
|
||||
}
|
||||
#[cfg(feature = "webhook")]
|
||||
{
|
||||
exists = exists || webhook::get_endpoint(config, name).is_ok();
|
||||
}
|
||||
|
||||
if !exists {
|
||||
http_bail!(NOT_FOUND, "endpoint '{name}' does not exist")
|
||||
|
432
proxmox-notify/src/api/webhook.rs
Normal file
432
proxmox-notify/src/api/webhook.rs
Normal file
@ -0,0 +1,432 @@
|
||||
//! CRUD API for webhook targets.
|
||||
//!
|
||||
//! All methods assume that the caller has already done any required permission checks.
|
||||
|
||||
use proxmox_http_error::HttpError;
|
||||
use proxmox_schema::property_string::PropertyString;
|
||||
|
||||
use crate::api::http_err;
|
||||
use crate::endpoints::webhook::{
|
||||
DeleteableWebhookProperty, KeyAndBase64Val, WebhookConfig, WebhookConfigUpdater,
|
||||
WebhookPrivateConfig, WEBHOOK_TYPENAME,
|
||||
};
|
||||
use crate::{http_bail, Config};
|
||||
|
||||
use super::remove_private_config_entry;
|
||||
use super::set_private_config_entry;
|
||||
|
||||
/// Get a list of all webhook endpoints.
|
||||
///
|
||||
/// The caller is responsible for any needed permission checks.
|
||||
/// Returns a list of all webhook endpoints or a [`HttpError`] if the config is
|
||||
/// erroneous (`500 Internal server error`).
|
||||
pub fn get_endpoints(config: &Config) -> Result<Vec<WebhookConfig>, HttpError> {
|
||||
let mut endpoints: Vec<WebhookConfig> = config
|
||||
.config
|
||||
.convert_to_typed_array(WEBHOOK_TYPENAME)
|
||||
.map_err(|e| http_err!(NOT_FOUND, "Could not fetch endpoints: {e}"))?;
|
||||
|
||||
for endpoint in &mut endpoints {
|
||||
let priv_config: WebhookPrivateConfig = config
|
||||
.private_config
|
||||
.lookup(WEBHOOK_TYPENAME, &endpoint.name)
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut secret_names = Vec::new();
|
||||
// We only return *which* secrets we have stored, but not their values.
|
||||
for secret in priv_config.secret {
|
||||
secret_names.push(
|
||||
KeyAndBase64Val {
|
||||
name: secret.name.clone(),
|
||||
value: None,
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
endpoint.secret = secret_names;
|
||||
}
|
||||
|
||||
Ok(endpoints)
|
||||
}
|
||||
|
||||
/// Get webhook endpoint with given `name`
|
||||
///
|
||||
/// The caller is responsible for any needed permission checks.
|
||||
/// Returns the endpoint or a [`HttpError`] if the endpoint was not found (`404 Not found`).
|
||||
pub fn get_endpoint(config: &Config, name: &str) -> Result<WebhookConfig, HttpError> {
|
||||
let mut endpoint: WebhookConfig = config
|
||||
.config
|
||||
.lookup(WEBHOOK_TYPENAME, name)
|
||||
.map_err(|_| http_err!(NOT_FOUND, "endpoint '{name}' not found"))?;
|
||||
|
||||
let priv_config: Option<WebhookPrivateConfig> = config
|
||||
.private_config
|
||||
.lookup(WEBHOOK_TYPENAME, &endpoint.name)
|
||||
.ok();
|
||||
|
||||
let mut secret_names = Vec::new();
|
||||
if let Some(priv_config) = priv_config {
|
||||
for secret in &priv_config.secret {
|
||||
secret_names.push(
|
||||
KeyAndBase64Val {
|
||||
name: secret.name.clone(),
|
||||
value: None,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
endpoint.secret = secret_names;
|
||||
|
||||
Ok(endpoint)
|
||||
}
|
||||
|
||||
/// Add a new webhook endpoint.
|
||||
///
|
||||
/// The caller is responsible for any needed permission checks.
|
||||
/// The caller also responsible for locking the configuration files.
|
||||
/// Returns a [`HttpError`] if:
|
||||
/// - the target name is already used (`400 Bad request`)
|
||||
/// - an entity with the same name already exists (`400 Bad request`)
|
||||
/// - the configuration could not be saved (`500 Internal server error`)
|
||||
pub fn add_endpoint(
|
||||
config: &mut Config,
|
||||
mut endpoint_config: WebhookConfig,
|
||||
) -> Result<(), HttpError> {
|
||||
super::ensure_unique(config, &endpoint_config.name)?;
|
||||
|
||||
let secrets = std::mem::take(&mut endpoint_config.secret);
|
||||
|
||||
set_private_config_entry(
|
||||
config,
|
||||
&WebhookPrivateConfig {
|
||||
name: endpoint_config.name.clone(),
|
||||
secret: secrets,
|
||||
},
|
||||
WEBHOOK_TYPENAME,
|
||||
&endpoint_config.name,
|
||||
)?;
|
||||
|
||||
config
|
||||
.config
|
||||
.set_data(&endpoint_config.name, WEBHOOK_TYPENAME, &endpoint_config)
|
||||
.map_err(|e| {
|
||||
http_err!(
|
||||
INTERNAL_SERVER_ERROR,
|
||||
"could not save endpoint '{}': {e}",
|
||||
endpoint_config.name
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Update existing webhook endpoint.
|
||||
///
|
||||
/// The caller is responsible for any needed permission checks.
|
||||
/// The caller also responsible for locking the configuration files.
|
||||
/// Returns a `HttpError` if:
|
||||
/// - the passed `digest` does not match (`400 Bad request`)
|
||||
/// - parameters are ill-formed (empty header value, invalid base64, unknown header/secret)
|
||||
/// (`400 Bad request`)
|
||||
/// - an entity with the same name already exists (`400 Bad request`)
|
||||
/// - the configuration could not be saved (`500 Internal server error`)
|
||||
pub fn update_endpoint(
|
||||
config: &mut Config,
|
||||
name: &str,
|
||||
config_updater: WebhookConfigUpdater,
|
||||
delete: Option<&[DeleteableWebhookProperty]>,
|
||||
digest: Option<&[u8]>,
|
||||
) -> Result<(), HttpError> {
|
||||
super::verify_digest(config, digest)?;
|
||||
|
||||
let mut endpoint = get_endpoint(config, name)?;
|
||||
endpoint.secret.clear();
|
||||
|
||||
let old_secrets = config
|
||||
.private_config
|
||||
.lookup::<WebhookPrivateConfig>(WEBHOOK_TYPENAME, name)
|
||||
.map_err(|err| http_err!(INTERNAL_SERVER_ERROR, "could not read secret config: {err}"))?
|
||||
.secret;
|
||||
|
||||
if let Some(delete) = delete {
|
||||
for deleteable_property in delete {
|
||||
match deleteable_property {
|
||||
DeleteableWebhookProperty::Comment => endpoint.comment = None,
|
||||
DeleteableWebhookProperty::Disable => endpoint.disable = None,
|
||||
DeleteableWebhookProperty::Header => endpoint.header = Vec::new(),
|
||||
DeleteableWebhookProperty::Body => endpoint.body = None,
|
||||
DeleteableWebhookProperty::Secret => {
|
||||
set_private_config_entry(
|
||||
config,
|
||||
&WebhookPrivateConfig {
|
||||
name: name.into(),
|
||||
secret: Vec::new(),
|
||||
},
|
||||
WEBHOOK_TYPENAME,
|
||||
name,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Destructuring makes sure we don't forget any members
|
||||
let WebhookConfigUpdater {
|
||||
url,
|
||||
body,
|
||||
header,
|
||||
method,
|
||||
disable,
|
||||
comment,
|
||||
secret,
|
||||
} = config_updater;
|
||||
|
||||
if let Some(url) = url {
|
||||
endpoint.url = url;
|
||||
}
|
||||
|
||||
if let Some(body) = body {
|
||||
endpoint.body = Some(body);
|
||||
}
|
||||
|
||||
if let Some(header) = header {
|
||||
for h in &header {
|
||||
if h.value.is_none() {
|
||||
http_bail!(BAD_REQUEST, "header '{}' has empty value", h.name);
|
||||
}
|
||||
if h.decode_value().is_err() {
|
||||
http_bail!(
|
||||
BAD_REQUEST,
|
||||
"header '{}' does not have valid base64 encoded data",
|
||||
h.name
|
||||
)
|
||||
}
|
||||
}
|
||||
endpoint.header = header;
|
||||
}
|
||||
|
||||
if let Some(method) = method {
|
||||
endpoint.method = method;
|
||||
}
|
||||
|
||||
if let Some(disable) = disable {
|
||||
endpoint.disable = Some(disable);
|
||||
}
|
||||
|
||||
if let Some(comment) = comment {
|
||||
endpoint.comment = Some(comment);
|
||||
}
|
||||
|
||||
if let Some(secret) = secret {
|
||||
let mut new_secrets: Vec<PropertyString<KeyAndBase64Val>> = Vec::new();
|
||||
|
||||
for new_secret in &secret {
|
||||
let sec = if new_secret.value.is_some() {
|
||||
// Updating or creating a secret
|
||||
|
||||
// Make sure it is valid base64 encoded data
|
||||
if new_secret.decode_value().is_err() {
|
||||
http_bail!(
|
||||
BAD_REQUEST,
|
||||
"secret '{}' does not have valid base64 encoded data",
|
||||
new_secret.name
|
||||
)
|
||||
}
|
||||
new_secret.clone()
|
||||
} else if let Some(old_secret) = old_secrets.iter().find(|v| v.name == new_secret.name)
|
||||
{
|
||||
// Keeping an already existing secret
|
||||
old_secret.clone()
|
||||
} else {
|
||||
http_bail!(BAD_REQUEST, "secret '{}' not known", new_secret.name);
|
||||
};
|
||||
|
||||
if new_secrets.iter().any(|s| sec.name == s.name) {
|
||||
http_bail!(BAD_REQUEST, "secret '{}' defined multiple times", sec.name)
|
||||
}
|
||||
|
||||
new_secrets.push(sec);
|
||||
}
|
||||
|
||||
set_private_config_entry(
|
||||
config,
|
||||
&WebhookPrivateConfig {
|
||||
name: name.into(),
|
||||
secret: new_secrets,
|
||||
},
|
||||
WEBHOOK_TYPENAME,
|
||||
name,
|
||||
)?;
|
||||
}
|
||||
|
||||
config
|
||||
.config
|
||||
.set_data(name, WEBHOOK_TYPENAME, &endpoint)
|
||||
.map_err(|e| {
|
||||
http_err!(
|
||||
INTERNAL_SERVER_ERROR,
|
||||
"could not save endpoint '{name}': {e}"
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Delete existing webhook endpoint.
|
||||
///
|
||||
/// The caller is responsible for any needed permission checks.
|
||||
/// The caller also responsible for locking the configuration files.
|
||||
/// Returns a `HttpError` if:
|
||||
/// - the entity does not exist (`404 Not found`)
|
||||
/// - the endpoint is still referenced by another entity (`400 Bad request`)
|
||||
pub fn delete_endpoint(config: &mut Config, name: &str) -> Result<(), HttpError> {
|
||||
// Check if the endpoint exists
|
||||
let _ = get_endpoint(config, name)?;
|
||||
super::ensure_safe_to_delete(config, name)?;
|
||||
|
||||
remove_private_config_entry(config, name)?;
|
||||
config.config.sections.remove(name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{api::test_helpers::empty_config, endpoints::webhook::HttpMethod};
|
||||
|
||||
use base64::encode;
|
||||
|
||||
pub fn add_default_webhook_endpoint(config: &mut Config) -> Result<(), HttpError> {
|
||||
add_endpoint(
|
||||
config,
|
||||
WebhookConfig {
|
||||
name: "webhook-endpoint".into(),
|
||||
method: HttpMethod::Post,
|
||||
url: "http://example.com/webhook".into(),
|
||||
header: vec![KeyAndBase64Val::new_with_plain_value(
|
||||
"Content-Type",
|
||||
"application/json",
|
||||
)
|
||||
.into()],
|
||||
body: Some(encode("this is the body")),
|
||||
comment: Some("comment".into()),
|
||||
disable: Some(false),
|
||||
secret: vec![KeyAndBase64Val::new_with_plain_value("token", "secret").into()],
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
assert!(get_endpoint(config, "webhook-endpoint").is_ok());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_not_existing_returns_error() -> Result<(), HttpError> {
|
||||
let mut config = empty_config();
|
||||
|
||||
assert!(update_endpoint(&mut config, "test", Default::default(), None, None).is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_invalid_digest_returns_error() -> Result<(), HttpError> {
|
||||
let mut config = empty_config();
|
||||
add_default_webhook_endpoint(&mut config)?;
|
||||
|
||||
assert!(update_endpoint(
|
||||
&mut config,
|
||||
"webhook-endpoint",
|
||||
Default::default(),
|
||||
None,
|
||||
Some(&[0; 32])
|
||||
)
|
||||
.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update() -> Result<(), HttpError> {
|
||||
let mut config = empty_config();
|
||||
add_default_webhook_endpoint(&mut config)?;
|
||||
|
||||
let digest = config.digest;
|
||||
|
||||
update_endpoint(
|
||||
&mut config,
|
||||
"webhook-endpoint",
|
||||
WebhookConfigUpdater {
|
||||
url: Some("http://new.example.com/webhook".into()),
|
||||
comment: Some("newcomment".into()),
|
||||
method: Some(HttpMethod::Put),
|
||||
// Keep the old token and set a new one
|
||||
secret: Some(vec![
|
||||
KeyAndBase64Val::new_with_plain_value("token2", "newsecret").into(),
|
||||
KeyAndBase64Val {
|
||||
name: "token".into(),
|
||||
value: None,
|
||||
}
|
||||
.into(),
|
||||
]),
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
Some(&digest),
|
||||
)?;
|
||||
|
||||
let endpoint = get_endpoint(&config, "webhook-endpoint")?;
|
||||
|
||||
assert_eq!(endpoint.url, "http://new.example.com/webhook".to_string());
|
||||
assert_eq!(endpoint.comment, Some("newcomment".to_string()));
|
||||
assert!(matches!(endpoint.method, HttpMethod::Put));
|
||||
|
||||
let secrets = config
|
||||
.private_config
|
||||
.lookup::<WebhookPrivateConfig>(WEBHOOK_TYPENAME, "webhook-endpoint")
|
||||
.unwrap()
|
||||
.secret;
|
||||
|
||||
assert_eq!(secrets[1].name, "token".to_string());
|
||||
assert_eq!(secrets[1].value, Some(encode("secret")));
|
||||
assert_eq!(secrets[0].name, "token2".to_string());
|
||||
assert_eq!(secrets[0].value, Some(encode("newsecret")));
|
||||
|
||||
// Test property deletion
|
||||
update_endpoint(
|
||||
&mut config,
|
||||
"webhook-endpoint",
|
||||
Default::default(),
|
||||
Some(&[
|
||||
DeleteableWebhookProperty::Comment,
|
||||
DeleteableWebhookProperty::Secret,
|
||||
]),
|
||||
None,
|
||||
)?;
|
||||
|
||||
let endpoint = get_endpoint(&config, "webhook-endpoint")?;
|
||||
assert_eq!(endpoint.comment, None);
|
||||
|
||||
let secrets = config
|
||||
.private_config
|
||||
.lookup::<WebhookPrivateConfig>(WEBHOOK_TYPENAME, "webhook-endpoint")
|
||||
.unwrap()
|
||||
.secret;
|
||||
|
||||
assert!(secrets.is_empty());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete() -> Result<(), HttpError> {
|
||||
let mut config = empty_config();
|
||||
add_default_webhook_endpoint(&mut config)?;
|
||||
|
||||
delete_endpoint(&mut config, "webhook-endpoint")?;
|
||||
assert!(delete_endpoint(&mut config, "webhook-endpoint").is_err());
|
||||
assert_eq!(get_endpoints(&config)?.len(), 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -57,6 +57,17 @@ fn config_init() -> SectionConfig {
|
||||
GOTIFY_SCHEMA,
|
||||
));
|
||||
}
|
||||
#[cfg(feature = "webhook")]
|
||||
{
|
||||
use crate::endpoints::webhook::{WebhookConfig, WEBHOOK_TYPENAME};
|
||||
|
||||
const WEBHOOK_SCHEMA: &ObjectSchema = WebhookConfig::API_SCHEMA.unwrap_object_schema();
|
||||
config.register_plugin(SectionConfigPlugin::new(
|
||||
WEBHOOK_TYPENAME.to_string(),
|
||||
Some(String::from("name")),
|
||||
WEBHOOK_SCHEMA,
|
||||
));
|
||||
}
|
||||
|
||||
const MATCHER_SCHEMA: &ObjectSchema = MatcherConfig::API_SCHEMA.unwrap_object_schema();
|
||||
config.register_plugin(SectionConfigPlugin::new(
|
||||
@ -110,6 +121,18 @@ fn private_config_init() -> SectionConfig {
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(feature = "webhook")]
|
||||
{
|
||||
use crate::endpoints::webhook::{WebhookPrivateConfig, WEBHOOK_TYPENAME};
|
||||
|
||||
const WEBHOOK_SCHEMA: &ObjectSchema =
|
||||
WebhookPrivateConfig::API_SCHEMA.unwrap_object_schema();
|
||||
config.register_plugin(SectionConfigPlugin::new(
|
||||
WEBHOOK_TYPENAME.to_string(),
|
||||
Some(String::from("name")),
|
||||
WEBHOOK_SCHEMA,
|
||||
));
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
@ -13,6 +14,8 @@ use crate::renderer::TemplateType;
|
||||
use crate::schema::ENTITY_NAME_SCHEMA;
|
||||
use crate::{renderer, Content, Endpoint, Error, Notification, Origin, Severity};
|
||||
|
||||
const HTTP_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
fn severity_to_priority(level: Severity) -> u32 {
|
||||
match level {
|
||||
Severity::Info => 1,
|
||||
@ -146,7 +149,7 @@ impl Endpoint for GotifyEndpoint {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let client = Client::new(options);
|
||||
let client = Client::new_with_timeout(options, HTTP_TIMEOUT);
|
||||
let uri = format!("{}/message", self.config.server);
|
||||
|
||||
client
|
||||
|
@ -4,5 +4,7 @@ pub mod gotify;
|
||||
pub mod sendmail;
|
||||
#[cfg(feature = "smtp")]
|
||||
pub mod smtp;
|
||||
#[cfg(feature = "webhook")]
|
||||
pub mod webhook;
|
||||
|
||||
mod common;
|
||||
|
@ -1,6 +1,4 @@
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use proxmox_sendmail::Mail;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use proxmox_schema::api_types::COMMENT_SCHEMA;
|
||||
@ -137,19 +135,18 @@ impl Endpoint for SendmailEndpoint {
|
||||
.clone()
|
||||
.unwrap_or_else(|| context().default_sendmail_author());
|
||||
|
||||
sendmail(
|
||||
&recipients_str,
|
||||
&subject,
|
||||
Some(&text_part),
|
||||
Some(&html_part),
|
||||
Some(&mailfrom),
|
||||
Some(&author),
|
||||
)
|
||||
.map_err(|err| Error::NotifyFailed(self.config.name.clone(), err.into()))
|
||||
let mut mail = Mail::new(&author, &mailfrom, &subject, &text_part)
|
||||
.with_html_alt(&html_part)
|
||||
.with_unmasked_recipients();
|
||||
|
||||
recipients_str.iter().for_each(|r| mail.add_recipient(r));
|
||||
|
||||
mail.send()
|
||||
.map_err(|err| Error::NotifyFailed(self.config.name.clone(), err.into()))
|
||||
}
|
||||
#[cfg(feature = "mail-forwarder")]
|
||||
Content::ForwardedMail { raw, uid, .. } => {
|
||||
forward(&recipients_str, &mailfrom, raw, *uid)
|
||||
Mail::forward(&recipients_str, &mailfrom, raw, *uid)
|
||||
.map_err(|err| Error::NotifyFailed(self.config.name.clone(), err.into()))
|
||||
}
|
||||
}
|
||||
@ -164,185 +161,3 @@ impl Endpoint for SendmailEndpoint {
|
||||
self.config.disable.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends multi-part mail with text and/or html to a list of recipients
|
||||
///
|
||||
/// Includes the header `Auto-Submitted: auto-generated`, so that auto-replies
|
||||
/// (i.e. OOO replies) won't trigger.
|
||||
/// ``sendmail`` is used for sending the mail.
|
||||
fn sendmail(
|
||||
mailto: &[&str],
|
||||
subject: &str,
|
||||
text: Option<&str>,
|
||||
html: Option<&str>,
|
||||
mailfrom: Option<&str>,
|
||||
author: Option<&str>,
|
||||
) -> Result<(), Error> {
|
||||
use std::fmt::Write as _;
|
||||
|
||||
if mailto.is_empty() {
|
||||
return Err(Error::Generic(
|
||||
"At least one recipient has to be specified!".into(),
|
||||
));
|
||||
}
|
||||
let mailfrom = mailfrom.unwrap_or("root");
|
||||
let recipients = mailto.join(",");
|
||||
let author = author.unwrap_or("Proxmox Backup Server");
|
||||
|
||||
let now = proxmox_time::epoch_i64();
|
||||
|
||||
let mut sendmail_process = match Command::new("/usr/sbin/sendmail")
|
||||
.arg("-B")
|
||||
.arg("8BITMIME")
|
||||
.arg("-f")
|
||||
.arg(mailfrom)
|
||||
.arg("--")
|
||||
.args(mailto)
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()
|
||||
{
|
||||
Err(err) => {
|
||||
return Err(Error::Generic(format!(
|
||||
"could not spawn sendmail process: {err}"
|
||||
)))
|
||||
}
|
||||
Ok(process) => process,
|
||||
};
|
||||
let mut is_multipart = false;
|
||||
if let (Some(_), Some(_)) = (text, html) {
|
||||
is_multipart = true;
|
||||
}
|
||||
|
||||
let mut body = String::new();
|
||||
let boundary = format!("----_=_NextPart_001_{}", now);
|
||||
if is_multipart {
|
||||
body.push_str("Content-Type: multipart/alternative;\n");
|
||||
let _ = writeln!(body, "\tboundary=\"{}\"", boundary);
|
||||
body.push_str("MIME-Version: 1.0\n");
|
||||
} else if !subject.is_ascii() {
|
||||
body.push_str("MIME-Version: 1.0\n");
|
||||
}
|
||||
if !subject.is_ascii() {
|
||||
let _ = writeln!(body, "Subject: =?utf-8?B?{}?=", base64::encode(subject));
|
||||
} else {
|
||||
let _ = writeln!(body, "Subject: {}", subject);
|
||||
}
|
||||
let _ = writeln!(body, "From: {} <{}>", author, mailfrom);
|
||||
let _ = writeln!(body, "To: {}", &recipients);
|
||||
let rfc2822_date = proxmox_time::epoch_to_rfc2822(now)
|
||||
.map_err(|err| Error::Generic(format!("failed to format time: {err}")))?;
|
||||
let _ = writeln!(body, "Date: {}", rfc2822_date);
|
||||
body.push_str("Auto-Submitted: auto-generated;\n");
|
||||
|
||||
if is_multipart {
|
||||
body.push('\n');
|
||||
body.push_str("This is a multi-part message in MIME format.\n");
|
||||
let _ = write!(body, "\n--{}\n", boundary);
|
||||
}
|
||||
if let Some(text) = text {
|
||||
body.push_str("Content-Type: text/plain;\n");
|
||||
body.push_str("\tcharset=\"UTF-8\"\n");
|
||||
body.push_str("Content-Transfer-Encoding: 8bit\n");
|
||||
body.push('\n');
|
||||
body.push_str(text);
|
||||
if is_multipart {
|
||||
let _ = write!(body, "\n--{}\n", boundary);
|
||||
}
|
||||
}
|
||||
if let Some(html) = html {
|
||||
body.push_str("Content-Type: text/html;\n");
|
||||
body.push_str("\tcharset=\"UTF-8\"\n");
|
||||
body.push_str("Content-Transfer-Encoding: 8bit\n");
|
||||
body.push('\n');
|
||||
body.push_str(html);
|
||||
if is_multipart {
|
||||
let _ = write!(body, "\n--{}--", boundary);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = sendmail_process
|
||||
.stdin
|
||||
.take()
|
||||
.unwrap()
|
||||
.write_all(body.as_bytes())
|
||||
{
|
||||
return Err(Error::Generic(format!(
|
||||
"couldn't write to sendmail stdin: {err}"
|
||||
)));
|
||||
};
|
||||
|
||||
// wait() closes stdin of the child
|
||||
if let Err(err) = sendmail_process.wait() {
|
||||
return Err(Error::Generic(format!(
|
||||
"sendmail did not exit successfully: {err}"
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Forwards an email message to a given list of recipients.
|
||||
///
|
||||
/// ``sendmail`` is used for sending the mail, thus `message` must be
|
||||
/// compatible with that (the message is piped into stdin unmodified).
|
||||
#[cfg(feature = "mail-forwarder")]
|
||||
fn forward(mailto: &[&str], mailfrom: &str, message: &[u8], uid: Option<u32>) -> Result<(), Error> {
|
||||
use std::os::unix::process::CommandExt;
|
||||
|
||||
if mailto.is_empty() {
|
||||
return Err(Error::Generic(
|
||||
"At least one recipient has to be specified!".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut builder = Command::new("/usr/sbin/sendmail");
|
||||
|
||||
builder
|
||||
.args([
|
||||
"-N", "never", // never send DSN (avoid mail loops)
|
||||
"-f", mailfrom, "--",
|
||||
])
|
||||
.args(mailto)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
|
||||
if let Some(uid) = uid {
|
||||
builder.uid(uid);
|
||||
}
|
||||
|
||||
let mut process = builder
|
||||
.spawn()
|
||||
.map_err(|err| Error::Generic(format!("could not spawn sendmail process: {err}")))?;
|
||||
|
||||
process
|
||||
.stdin
|
||||
.take()
|
||||
.unwrap()
|
||||
.write_all(message)
|
||||
.map_err(|err| Error::Generic(format!("couldn't write to sendmail stdin: {err}")))?;
|
||||
|
||||
process
|
||||
.wait()
|
||||
.map_err(|err| Error::Generic(format!("sendmail did not exit successfully: {err}")))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn email_without_recipients() {
|
||||
let result = sendmail(
|
||||
&[],
|
||||
"Subject2",
|
||||
None,
|
||||
Some("<b>HTML</b>"),
|
||||
None,
|
||||
Some("test1"),
|
||||
);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
554
proxmox-notify/src/endpoints/webhook.rs
Normal file
554
proxmox-notify/src/endpoints/webhook.rs
Normal file
@ -0,0 +1,554 @@
|
||||
//! This endpoint implements a generic webhook target, allowing users to send notifications through
|
||||
//! a highly customizable HTTP request.
|
||||
//!
|
||||
//! The configuration options include specifying the HTTP method, URL, headers, and body.
|
||||
//! URLs, headers, and the body support template expansion using the [`handlebars`] templating engine.
|
||||
//! For secure handling of passwords or tokens, these values can be stored as secrets.
|
||||
//! Secrets are kept in a private configuration file, accessible only by root, and are not retrievable via the API.
|
||||
//! Within templates, secrets can be referenced using `{{ secrets.<name> }}`.
|
||||
//! Additionally, we take measures to prevent secrets from appearing in logs or error messages.
|
||||
use std::time::Duration;
|
||||
|
||||
use handlebars::{
|
||||
Context as HandlebarsContext, Handlebars, Helper, HelperResult, Output, RenderContext,
|
||||
RenderError as HandlebarsRenderError,
|
||||
};
|
||||
use http::Request;
|
||||
use percent_encoding::AsciiSet;
|
||||
use proxmox_schema::property_string::PropertyString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use proxmox_http::client::sync::Client;
|
||||
use proxmox_http::{HttpClient, HttpOptions, ProxyConfig};
|
||||
use proxmox_schema::api_types::{COMMENT_SCHEMA, HTTP_URL_SCHEMA};
|
||||
use proxmox_schema::{api, ApiStringFormat, ApiType, Schema, StringSchema, Updater};
|
||||
|
||||
use crate::context::context;
|
||||
use crate::renderer::TemplateType;
|
||||
use crate::schema::ENTITY_NAME_SCHEMA;
|
||||
use crate::{renderer, Content, Endpoint, Error, Notification, Origin};
|
||||
|
||||
/// This will be used as a section type in the public/private configuration file.
|
||||
pub(crate) const WEBHOOK_TYPENAME: &str = "webhook";
|
||||
|
||||
const HTTP_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
#[api]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
/// HTTP Method to use.
|
||||
pub enum HttpMethod {
|
||||
/// HTTP POST
|
||||
#[default]
|
||||
Post,
|
||||
/// HTTP PUT
|
||||
Put,
|
||||
/// HTTP GET
|
||||
Get,
|
||||
}
|
||||
|
||||
// We only ever need a &str, so we rather implement this
|
||||
// instead of Display.
|
||||
impl From<HttpMethod> for &str {
|
||||
fn from(value: HttpMethod) -> Self {
|
||||
match value {
|
||||
HttpMethod::Post => "POST",
|
||||
HttpMethod::Put => "PUT",
|
||||
HttpMethod::Get => "GET",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[api(
|
||||
properties: {
|
||||
name: {
|
||||
schema: ENTITY_NAME_SCHEMA,
|
||||
},
|
||||
url: {
|
||||
schema: HTTP_URL_SCHEMA,
|
||||
},
|
||||
comment: {
|
||||
optional: true,
|
||||
schema: COMMENT_SCHEMA,
|
||||
},
|
||||
header: {
|
||||
type: Array,
|
||||
items: {
|
||||
schema: KEY_AND_BASE64_VALUE_SCHEMA,
|
||||
},
|
||||
optional: true,
|
||||
},
|
||||
secret: {
|
||||
type: Array,
|
||||
items: {
|
||||
schema: KEY_AND_BASE64_VALUE_SCHEMA,
|
||||
},
|
||||
optional: true,
|
||||
},
|
||||
}
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Updater, Default, Clone)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
/// Config for Webhook notification endpoints
|
||||
pub struct WebhookConfig {
|
||||
/// Name of the endpoint.
|
||||
#[updater(skip)]
|
||||
pub name: String,
|
||||
|
||||
pub method: HttpMethod,
|
||||
|
||||
/// Webhook URL. Supports templating.
|
||||
pub url: String,
|
||||
/// Array of HTTP headers. Each entry is a property string with a name and a value.
|
||||
/// The value property contains the header in base64 encoding. Supports templating.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[updater(serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub header: Vec<PropertyString<KeyAndBase64Val>>,
|
||||
/// The HTTP body to send. Supports templating.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub body: Option<String>,
|
||||
|
||||
/// Comment.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub comment: Option<String>,
|
||||
/// Disable this target.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub disable: Option<bool>,
|
||||
/// Origin of this config entry.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[updater(skip)]
|
||||
pub origin: Option<Origin>,
|
||||
/// Array of secrets. Each entry is a property string with a name and an optional value.
|
||||
/// The value property contains the secret in base64 encoding.
|
||||
/// For any API endpoints returning the endpoint config,
|
||||
/// only the secret name but not the value will be returned.
|
||||
/// When updating the config, also send all secrets that you want
|
||||
/// to keep, setting only the name but not the value. Can be accessed from templates.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[updater(serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub secret: Vec<PropertyString<KeyAndBase64Val>>,
|
||||
}
|
||||
|
||||
#[api(
|
||||
properties: {
|
||||
name: {
|
||||
schema: ENTITY_NAME_SCHEMA,
|
||||
},
|
||||
secret: {
|
||||
type: Array,
|
||||
items: {
|
||||
schema: KEY_AND_BASE64_VALUE_SCHEMA,
|
||||
},
|
||||
optional: true,
|
||||
},
|
||||
}
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Clone, Updater, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
/// Private configuration for Webhook notification endpoints.
|
||||
/// This config will be saved to a separate configuration file with stricter
|
||||
/// permissions (root:root 0600).
|
||||
pub struct WebhookPrivateConfig {
|
||||
/// Name of the endpoint
|
||||
#[updater(skip)]
|
||||
pub name: String,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[updater(serde(skip_serializing_if = "Option::is_none"))]
|
||||
/// Array of secrets. Each entry is a property string with a name,
|
||||
/// and a value property. The value property contains the secret
|
||||
/// in base64 encoding. Can be accessed from templates.
|
||||
pub secret: Vec<PropertyString<KeyAndBase64Val>>,
|
||||
}
|
||||
|
||||
/// A Webhook notification endpoint.
|
||||
pub struct WebhookEndpoint {
|
||||
pub config: WebhookConfig,
|
||||
pub private_config: WebhookPrivateConfig,
|
||||
}
|
||||
|
||||
#[api]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
/// Webhook configuration properties that can be deleted.
|
||||
pub enum DeleteableWebhookProperty {
|
||||
/// Delete `comment`.
|
||||
Comment,
|
||||
/// Delete `disable`.
|
||||
Disable,
|
||||
/// Delete `header`.
|
||||
Header,
|
||||
/// Delete `body`.
|
||||
Body,
|
||||
/// Delete `secret`.
|
||||
Secret,
|
||||
}
|
||||
|
||||
#[api]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
/// Datatype used to represent key-value pairs, the value
|
||||
/// being encoded in base64.
|
||||
pub struct KeyAndBase64Val {
|
||||
/// Name
|
||||
pub name: String,
|
||||
/// Base64 encoded value
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
impl KeyAndBase64Val {
|
||||
#[cfg(test)]
|
||||
pub fn new_with_plain_value(name: &str, value: &str) -> Self {
|
||||
let value = base64::encode(value);
|
||||
|
||||
Self {
|
||||
name: name.into(),
|
||||
value: Some(value),
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode the contained value, returning the plaintext value
|
||||
///
|
||||
/// Returns an error if the contained value is not valid base64-encoded
|
||||
/// text.
|
||||
pub fn decode_value(&self) -> Result<String, Error> {
|
||||
let value = self.value.as_deref().unwrap_or_default();
|
||||
let bytes = base64::decode(value).map_err(|_| {
|
||||
Error::Generic(format!(
|
||||
"could not decode base64 value with key '{}'",
|
||||
self.name
|
||||
))
|
||||
})?;
|
||||
let value = String::from_utf8(bytes).map_err(|_| {
|
||||
Error::Generic(format!(
|
||||
"could not decode UTF8 string from base64, key '{}'",
|
||||
self.name
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub const KEY_AND_BASE64_VALUE_SCHEMA: Schema =
|
||||
StringSchema::new("String schema for pairs of keys and base64 encoded values")
|
||||
.format(&ApiStringFormat::PropertyString(
|
||||
&KeyAndBase64Val::API_SCHEMA,
|
||||
))
|
||||
.schema();
|
||||
|
||||
impl Endpoint for WebhookEndpoint {
|
||||
/// Send a notification to a webhook endpoint.
|
||||
fn send(&self, notification: &Notification) -> Result<(), Error> {
|
||||
let request = self.build_request(notification)?;
|
||||
|
||||
self.create_client()?
|
||||
.request(request)
|
||||
.map_err(|err| self.mask_secret_in_error(err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the name of the endpoint.
|
||||
fn name(&self) -> &str {
|
||||
&self.config.name
|
||||
}
|
||||
|
||||
/// Check if the endpoint is disabled
|
||||
fn disabled(&self) -> bool {
|
||||
self.config.disable.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl WebhookEndpoint {
|
||||
fn create_client(&self) -> Result<Client, Error> {
|
||||
let proxy_config = context()
|
||||
.http_proxy_config()
|
||||
.map(|url| ProxyConfig::parse_proxy_url(&url))
|
||||
.transpose()
|
||||
.map_err(|err| Error::NotifyFailed(self.name().to_string(), err.into()))?;
|
||||
|
||||
let options = HttpOptions {
|
||||
proxy_config,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ok(Client::new_with_timeout(options, HTTP_TIMEOUT))
|
||||
}
|
||||
|
||||
fn build_request(&self, notification: &Notification) -> Result<Request<String>, Error> {
|
||||
let (title, message) = match ¬ification.content {
|
||||
Content::Template {
|
||||
template_name,
|
||||
data,
|
||||
} => {
|
||||
let rendered_title =
|
||||
renderer::render_template(TemplateType::Subject, template_name, data)?;
|
||||
let rendered_message =
|
||||
renderer::render_template(TemplateType::PlaintextBody, template_name, data)?;
|
||||
|
||||
(rendered_title, rendered_message)
|
||||
}
|
||||
#[cfg(feature = "mail-forwarder")]
|
||||
Content::ForwardedMail { title, body, .. } => (title.clone(), body.clone()),
|
||||
};
|
||||
|
||||
let mut fields = Map::new();
|
||||
|
||||
for (field_name, field_value) in ¬ification.metadata.additional_fields {
|
||||
fields.insert(field_name.clone(), Value::String(field_value.to_string()));
|
||||
}
|
||||
|
||||
let mut secrets = Map::new();
|
||||
|
||||
for secret in &self.private_config.secret {
|
||||
let value = secret.decode_value()?;
|
||||
secrets.insert(secret.name.clone(), Value::String(value));
|
||||
}
|
||||
|
||||
let data = json!({
|
||||
"title": &title,
|
||||
"message": &message,
|
||||
"severity": notification.metadata.severity,
|
||||
"timestamp": notification.metadata.timestamp,
|
||||
"fields": fields,
|
||||
"secrets": secrets,
|
||||
});
|
||||
|
||||
let handlebars = setup_handlebars();
|
||||
let body_template = self.base64_decode(self.config.body.as_deref().unwrap_or_default())?;
|
||||
|
||||
let body = handlebars
|
||||
.render_template(&body_template, &data)
|
||||
.map_err(|err| self.mask_secret_in_error(err))
|
||||
.map_err(|err| Error::Generic(format!("failed to render webhook body: {err}")))?;
|
||||
|
||||
let url = handlebars
|
||||
.render_template(&self.config.url, &data)
|
||||
.map_err(|err| self.mask_secret_in_error(err))
|
||||
.map_err(|err| Error::Generic(format!("failed to render webhook url: {err}")))?;
|
||||
|
||||
let method: &str = self.config.method.into();
|
||||
let mut builder = http::Request::builder().uri(url).method(method);
|
||||
|
||||
for header in &self.config.header {
|
||||
let value = header.decode_value()?;
|
||||
|
||||
let value = handlebars
|
||||
.render_template(&value, &data)
|
||||
.map_err(|err| self.mask_secret_in_error(err))
|
||||
.map_err(|err| {
|
||||
Error::Generic(format!(
|
||||
"failed to render header value template: {value}: {err}"
|
||||
))
|
||||
})?;
|
||||
|
||||
builder = builder.header(header.name.clone(), value);
|
||||
}
|
||||
|
||||
let request = builder
|
||||
.body(body)
|
||||
.map_err(|err| self.mask_secret_in_error(err))
|
||||
.map_err(|err| Error::Generic(format!("failed to build http request: {err}")))?;
|
||||
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
fn base64_decode(&self, s: &str) -> Result<String, Error> {
|
||||
// Also here, TODO: revisit Error variants for the *whole* crate.
|
||||
let s = base64::decode(s)
|
||||
.map_err(|err| Error::Generic(format!("could not decode base64 value: {err}")))?;
|
||||
|
||||
String::from_utf8(s).map_err(|err| {
|
||||
Error::Generic(format!(
|
||||
"base64 encoded value did not contain valid utf8: {err}"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Mask secrets in errors to avoid them showing up in error messages and log files
|
||||
///
|
||||
/// Use this for any error from third-party code where you are not 100%
|
||||
/// sure whether it could leak the content of secrets in the error.
|
||||
/// For instance, the http client will contain the URL, including
|
||||
/// any URL parameters that could contain tokens.
|
||||
///
|
||||
/// This function will only mask exact matches, but this should suffice
|
||||
/// for the majority of cases.
|
||||
fn mask_secret_in_error(&self, error: impl std::fmt::Display) -> Error {
|
||||
let mut s = error.to_string();
|
||||
|
||||
for secret_value in &self.private_config.secret {
|
||||
match secret_value.decode_value() {
|
||||
Ok(value) => s = s.replace(&value, "<masked>"),
|
||||
Err(e) => return e,
|
||||
}
|
||||
}
|
||||
|
||||
Error::Generic(s)
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_handlebars() -> Handlebars<'static> {
|
||||
let mut handlebars = Handlebars::new();
|
||||
|
||||
handlebars.register_helper("url-encode", Box::new(handlebars_percent_encode));
|
||||
handlebars.register_helper("json", Box::new(handlebars_json));
|
||||
handlebars.register_helper("escape", Box::new(handlebars_escape));
|
||||
|
||||
// There is no escape.
|
||||
handlebars.register_escape_fn(handlebars::no_escape);
|
||||
|
||||
handlebars
|
||||
}
|
||||
|
||||
fn handlebars_percent_encode(
|
||||
h: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &HandlebarsContext,
|
||||
_rc: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
let param0 = h
|
||||
.param(0)
|
||||
.and_then(|v| v.value().as_str())
|
||||
.ok_or_else(|| HandlebarsRenderError::new("url-encode: missing parameter"))?;
|
||||
|
||||
// See https://developer.mozilla.org/en-US/docs/Glossary/Percent-encoding
|
||||
const FRAGMENT: &AsciiSet = &percent_encoding::CONTROLS
|
||||
.add(b':')
|
||||
.add(b'/')
|
||||
.add(b'?')
|
||||
.add(b'#')
|
||||
.add(b'[')
|
||||
.add(b']')
|
||||
.add(b'@')
|
||||
.add(b'!')
|
||||
.add(b'$')
|
||||
.add(b'&')
|
||||
.add(b'\'')
|
||||
.add(b'(')
|
||||
.add(b')')
|
||||
.add(b'*')
|
||||
.add(b'+')
|
||||
.add(b',')
|
||||
.add(b';')
|
||||
.add(b'=')
|
||||
.add(b'%')
|
||||
.add(b' ');
|
||||
let a = percent_encoding::utf8_percent_encode(param0, FRAGMENT);
|
||||
|
||||
out.write(&a.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handlebars_json(
|
||||
h: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &HandlebarsContext,
|
||||
_rc: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
let param0 = h
|
||||
.param(0)
|
||||
.map(|v| v.value())
|
||||
.ok_or_else(|| HandlebarsRenderError::new("json: missing parameter"))?;
|
||||
|
||||
let json = serde_json::to_string(param0)?;
|
||||
out.write(&json)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handlebars_escape(
|
||||
h: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &HandlebarsContext,
|
||||
_rc: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
let text = h
|
||||
.param(0)
|
||||
.and_then(|v| v.value().as_str())
|
||||
.ok_or_else(|| HandlebarsRenderError::new("escape: missing text parameter"))?;
|
||||
|
||||
let val = Value::String(text.to_string());
|
||||
let json = serde_json::to_string(&val)?;
|
||||
out.write(&json[1..json.len() - 1])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::*;
|
||||
use crate::Severity;
|
||||
|
||||
#[test]
|
||||
fn test_build_request() -> Result<(), Error> {
|
||||
let data = HashMap::from_iter([
|
||||
("hello".into(), "hello world".into()),
|
||||
("test".into(), "escaped\nstring".into()),
|
||||
]);
|
||||
|
||||
let body_template = r#"
|
||||
{{ fields.test }}
|
||||
{{ escape fields.test }}
|
||||
|
||||
{{ json fields }}
|
||||
{{ json fields.hello }}
|
||||
|
||||
{{ url-encode fields.hello }}
|
||||
|
||||
{{ json severity }}
|
||||
|
||||
"#;
|
||||
|
||||
let expected_body = r#"
|
||||
escaped
|
||||
string
|
||||
escaped\nstring
|
||||
|
||||
{"hello":"hello world","test":"escaped\nstring"}
|
||||
"hello world"
|
||||
|
||||
hello%20world
|
||||
|
||||
"info"
|
||||
|
||||
"#;
|
||||
|
||||
let endpoint = WebhookEndpoint {
|
||||
config: WebhookConfig {
|
||||
name: "test".into(),
|
||||
method: HttpMethod::Post,
|
||||
url: "http://localhost/{{ url-encode fields.hello }}".into(),
|
||||
header: vec![
|
||||
KeyAndBase64Val::new_with_plain_value("X-Severity", "{{ severity }}").into(),
|
||||
],
|
||||
body: Some(base64::encode(body_template)),
|
||||
..Default::default()
|
||||
},
|
||||
private_config: WebhookPrivateConfig {
|
||||
name: "test".into(),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
let notification = Notification::from_template(Severity::Info, "foo", json!({}), data);
|
||||
|
||||
let request = endpoint.build_request(¬ification)?;
|
||||
|
||||
assert_eq!(request.uri(), "http://localhost/hello%20world");
|
||||
assert_eq!(request.body(), expected_body);
|
||||
assert_eq!(request.method(), "POST");
|
||||
|
||||
assert_eq!(request.headers().get("X-Severity").unwrap(), "info");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -500,6 +500,23 @@ impl Bus {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "webhook")]
|
||||
{
|
||||
use endpoints::webhook::WEBHOOK_TYPENAME;
|
||||
use endpoints::webhook::{WebhookConfig, WebhookEndpoint, WebhookPrivateConfig};
|
||||
endpoints.extend(
|
||||
parse_endpoints_with_private_config!(
|
||||
config,
|
||||
WebhookConfig,
|
||||
WebhookPrivateConfig,
|
||||
WebhookEndpoint,
|
||||
WEBHOOK_TYPENAME
|
||||
)?
|
||||
.into_iter()
|
||||
.map(|e| (e.name().into(), e)),
|
||||
);
|
||||
}
|
||||
|
||||
let matchers = config
|
||||
.config
|
||||
.convert_to_typed_array(MATCHER_TYPENAME)
|
||||
|
@ -290,7 +290,6 @@ pub fn render_template(
|
||||
(None, TemplateType::HtmlBody) => {
|
||||
ty = TemplateType::PlaintextBody;
|
||||
let plaintext_filename = format!("{template}-{suffix}", suffix = ty.file_suffix());
|
||||
log::info!("html template '{filename}' not found, falling back to plain text template '{plaintext_filename}'");
|
||||
(
|
||||
context::context().lookup_template(&plaintext_filename, None)?,
|
||||
true,
|
||||
@ -329,8 +328,8 @@ mod tests {
|
||||
Some("1 KiB".to_string())
|
||||
);
|
||||
|
||||
assert_eq!(value_to_duration(&json!(60)), Some("1min ".to_string()));
|
||||
assert_eq!(value_to_duration(&json!("60")), Some("1min ".to_string()));
|
||||
assert_eq!(value_to_duration(&json!(60)), Some("1m".to_string()));
|
||||
assert_eq!(value_to_duration(&json!("60")), Some("1m".to_string()));
|
||||
|
||||
// The rendered value is in localtime, so we only check if the result is `Some`...
|
||||
// ... otherwise the test will break in another timezone :S
|
||||
|
@ -1,10 +1,10 @@
|
||||
Source: rust-proxmox-openid
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-anyhow-1+default-dev <!nocheck>,
|
||||
librust-http-0.2+default-dev <!nocheck>,
|
||||
@ -21,9 +21,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-ureq-2+gzip-dev (>= 2.4-~~) <!nocheck>,
|
||||
librust-ureq-2+native-tls-dev (>= 2.4-~~) <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git:
|
||||
Vcs-Browser:
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-openid
|
||||
Rules-Requires-Root: no
|
||||
|
||||
@ -54,5 +55,5 @@ Provides:
|
||||
librust-proxmox-openid-0.10+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-openid-0.10.3-dev (= ${binary:Version}),
|
||||
librust-proxmox-openid-0.10.3+default-dev (= ${binary:Version})
|
||||
Description: Rust crate "proxmox-openid" - Rust source code
|
||||
Description: Base for openid authentication in proxmox products - Rust source code
|
||||
Source code for Debianized Rust crate "proxmox-openid"
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-product-config
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -11,9 +11,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-proxmox-sys-0.6+default-dev <!nocheck>,
|
||||
librust-proxmox-sys-0.6+timer-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-product-config
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "proxmox-rest-server"
|
||||
description = "REST server implementation"
|
||||
version = "0.8.0"
|
||||
version = "0.8.4"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
@ -1,3 +1,32 @@
|
||||
rust-proxmox-rest-server (0.8.4-1) bookworm; urgency=medium
|
||||
|
||||
* add custom handlebars escape fn and skip escaping the '=' charater. This
|
||||
is required to support base64 encoded values.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 25 Nov 2024 17:14:25 +0100
|
||||
|
||||
rust-proxmox-rest-server (0.8.3-1) bookworm; urgency=medium
|
||||
|
||||
* connection: drop logging peek-length, which was only used for debugging
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 15 Nov 2024 10:31:14 +0100
|
||||
|
||||
rust-proxmox-rest-server (0.8.2-1) bookworm; urgency=medium
|
||||
|
||||
* fix #5868: rest-server: handshake detection: avoid infinite loop on
|
||||
connections abort
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 14 Nov 2024 14:34:09 +0100
|
||||
|
||||
rust-proxmox-rest-server (0.8.1-1) bookworm; urgency=medium
|
||||
|
||||
* honor passed cipher suite & list in TlsAcceptorBuilder
|
||||
|
||||
* provide better error messages in case key or certificate files are
|
||||
inaccessible
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 08 Nov 2024 12:02:10 +0100
|
||||
|
||||
rust-proxmox-rest-server (0.8.0-1) bookworm; urgency=medium
|
||||
|
||||
* rename old "streaming" api to "serializing" (as it was not truly
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-rest-server
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -20,7 +20,7 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-proxmox-compression-0.2+default-dev (>= 0.2.4-~~) <!nocheck>,
|
||||
librust-proxmox-daemon-0.1+default-dev <!nocheck>,
|
||||
librust-proxmox-lang-1+default-dev (>= 1.3-~~) <!nocheck>,
|
||||
librust-proxmox-log-0.2+default-dev (>= 0.2.3-~~) <!nocheck>,
|
||||
librust-proxmox-log-0.2+default-dev (>= 0.2.5-~~) <!nocheck>,
|
||||
librust-proxmox-router-3+default-dev <!nocheck>,
|
||||
librust-proxmox-schema-3+api-macro-dev (>= 3.1.2-~~) <!nocheck>,
|
||||
librust-proxmox-schema-3+default-dev (>= 3.1.2-~~) <!nocheck>,
|
||||
@ -43,9 +43,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-tracing-0.1+default-dev <!nocheck>,
|
||||
librust-url-2+default-dev (>= 2.2-~~) <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-rest-server
|
||||
Rules-Requires-Root: no
|
||||
|
||||
@ -68,7 +69,7 @@ Depends:
|
||||
librust-proxmox-compression-0.2+default-dev (>= 0.2.4-~~),
|
||||
librust-proxmox-daemon-0.1+default-dev,
|
||||
librust-proxmox-lang-1+default-dev (>= 1.3-~~),
|
||||
librust-proxmox-log-0.2+default-dev (>= 0.2.3-~~),
|
||||
librust-proxmox-log-0.2+default-dev (>= 0.2.5-~~),
|
||||
librust-proxmox-router-3+default-dev,
|
||||
librust-proxmox-schema-3+api-macro-dev (>= 3.1.2-~~),
|
||||
librust-proxmox-schema-3+default-dev (>= 3.1.2-~~),
|
||||
@ -99,8 +100,8 @@ Provides:
|
||||
librust-proxmox-rest-server-0+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-rest-server-0.8-dev (= ${binary:Version}),
|
||||
librust-proxmox-rest-server-0.8+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-rest-server-0.8.0-dev (= ${binary:Version}),
|
||||
librust-proxmox-rest-server-0.8.0+default-dev (= ${binary:Version})
|
||||
librust-proxmox-rest-server-0.8.4-dev (= ${binary:Version}),
|
||||
librust-proxmox-rest-server-0.8.4+default-dev (= ${binary:Version})
|
||||
Description: REST server implementation - Rust source code
|
||||
Source code for Debianized Rust crate "proxmox-rest-server"
|
||||
|
||||
@ -110,12 +111,12 @@ Multi-Arch: same
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
librust-proxmox-rest-server-dev (= ${binary:Version}),
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.2-~~),
|
||||
librust-proxmox-http-0.9+rate-limited-stream-dev (>= 0.9.2-~~)
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.4-~~),
|
||||
librust-proxmox-http-0.9+rate-limited-stream-dev (>= 0.9.4-~~)
|
||||
Provides:
|
||||
librust-proxmox-rest-server-0+rate-limited-stream-dev (= ${binary:Version}),
|
||||
librust-proxmox-rest-server-0.8+rate-limited-stream-dev (= ${binary:Version}),
|
||||
librust-proxmox-rest-server-0.8.0+rate-limited-stream-dev (= ${binary:Version})
|
||||
librust-proxmox-rest-server-0.8.4+rate-limited-stream-dev (= ${binary:Version})
|
||||
Description: REST server implementation - feature "rate-limited-stream"
|
||||
This metapackage enables feature "rate-limited-stream" for the Rust proxmox-
|
||||
rest-server crate, by pulling in any additional dependencies needed by that
|
||||
@ -131,7 +132,7 @@ Depends:
|
||||
Provides:
|
||||
librust-proxmox-rest-server-0+templates-dev (= ${binary:Version}),
|
||||
librust-proxmox-rest-server-0.8+templates-dev (= ${binary:Version}),
|
||||
librust-proxmox-rest-server-0.8.0+templates-dev (= ${binary:Version})
|
||||
librust-proxmox-rest-server-0.8.4+templates-dev (= ${binary:Version})
|
||||
Description: REST server implementation - feature "templates"
|
||||
This metapackage enables feature "templates" for the Rust proxmox-rest-server
|
||||
crate, by pulling in any additional dependencies needed by that feature.
|
||||
|
@ -62,7 +62,7 @@ impl ApiConfig {
|
||||
privileged_addr: None,
|
||||
|
||||
#[cfg(feature = "templates")]
|
||||
templates: Default::default(),
|
||||
templates: templates::Templates::with_escape_fn(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -335,6 +335,31 @@ mod templates {
|
||||
}
|
||||
|
||||
impl Templates {
|
||||
pub fn with_escape_fn() -> Templates {
|
||||
let mut registry = Handlebars::new();
|
||||
// This is the same as the default `html_escape` fn in handlebars, **but** it does not
|
||||
// escape the '='. This is to preserve base64 values.
|
||||
registry.register_escape_fn(|value| {
|
||||
let mut output = String::new();
|
||||
for c in value.chars() {
|
||||
match c {
|
||||
'<' => output.push_str("<"),
|
||||
'>' => output.push_str(">"),
|
||||
'"' => output.push_str("""),
|
||||
'&' => output.push_str("&"),
|
||||
'\'' => output.push_str("'"),
|
||||
'`' => output.push_str("`"),
|
||||
_ => output.push(c),
|
||||
}
|
||||
}
|
||||
output
|
||||
});
|
||||
Self {
|
||||
templates: RwLock::new(registry),
|
||||
template_files: RwLock::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register<P>(&self, name: &str, path: P) -> Result<(), Error>
|
||||
where
|
||||
P: Into<PathBuf>,
|
||||
|
@ -12,13 +12,13 @@ use std::pin::{pin, Pin};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{format_err, Context as _, Error};
|
||||
use anyhow::{format_err, Context, Error};
|
||||
use futures::FutureExt;
|
||||
use hyper::server::accept;
|
||||
use openssl::ec::{EcGroup, EcKey};
|
||||
use openssl::nid::Nid;
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
|
||||
use openssl::ssl::{SslAcceptor, SslMethod};
|
||||
use openssl::x509::X509;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::sync::mpsc;
|
||||
@ -78,6 +78,17 @@ impl TlsAcceptorBuilder {
|
||||
pub fn build(self) -> Result<SslAcceptor, Error> {
|
||||
let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap();
|
||||
|
||||
if let Some(cipher_suites) = self.cipher_suites.as_deref() {
|
||||
acceptor
|
||||
.set_ciphersuites(cipher_suites)
|
||||
.context("failed to set tls acceptor cipher suites")?;
|
||||
}
|
||||
if let Some(cipher_list) = self.cipher_list.as_deref() {
|
||||
acceptor
|
||||
.set_cipher_list(cipher_list)
|
||||
.context("failed to set tls acceptor cipher list")?;
|
||||
}
|
||||
|
||||
match self.tls {
|
||||
Some(Tls::KeyCert(key, cert)) => {
|
||||
acceptor
|
||||
@ -88,9 +99,17 @@ impl TlsAcceptorBuilder {
|
||||
.context("failed to set tls acceptor certificate")?;
|
||||
}
|
||||
Some(Tls::FilesPem(key, cert)) => {
|
||||
let key_content = std::fs::read(&key)
|
||||
.with_context(|| format!("Failed to read from private key file {key:?}"))?;
|
||||
acceptor
|
||||
.set_private_key_file(key, SslFiletype::PEM)
|
||||
.set_private_key(PKey::private_key_from_pem(&key_content)?.as_ref())
|
||||
.context("failed to set tls acceptor private key file")?;
|
||||
|
||||
{
|
||||
// Check the permissions by opening the file
|
||||
let _cert_fd = std::fs::File::open(&cert)
|
||||
.with_context(|| format!("Failed to open certificate at {cert:?}"))?;
|
||||
}
|
||||
acceptor
|
||||
.set_certificate_chain_file(cert)
|
||||
.context("failed to set tls acceptor certificate chain file")?;
|
||||
@ -458,6 +477,7 @@ impl AcceptBuilder {
|
||||
const HANDSHAKE_BYTES_LEN: usize = 5;
|
||||
|
||||
let future = async {
|
||||
let mut previous_peek_len = 0;
|
||||
incoming_stream
|
||||
.async_io(tokio::io::Interest::READABLE, || {
|
||||
let mut buf = [0; HANDSHAKE_BYTES_LEN];
|
||||
@ -481,7 +501,14 @@ impl AcceptBuilder {
|
||||
// This means we will peek into the stream's queue until we got
|
||||
// HANDSHAKE_BYTE_LEN bytes or an error.
|
||||
Ok(peek_len) if peek_len < HANDSHAKE_BYTES_LEN => {
|
||||
Err(io::ErrorKind::WouldBlock.into())
|
||||
// if we detect the same peek len again but still got a readable stream,
|
||||
// the connection was probably closed, so abort here
|
||||
if peek_len == previous_peek_len {
|
||||
Err(io::ErrorKind::ConnectionAborted.into())
|
||||
} else {
|
||||
previous_peek_len = peek_len;
|
||||
Err(io::ErrorKind::WouldBlock.into())
|
||||
}
|
||||
}
|
||||
// Either we got Ok(HANDSHAKE_BYTES_LEN) or some error.
|
||||
res => res.map(|_| contains_tls_handshake_fragment(&buf)),
|
||||
|
@ -139,7 +139,7 @@ impl WorkerTaskSetup {
|
||||
.clone()
|
||||
.perm(nix::sys::stat::Mode::from_bits_truncate(0o660));
|
||||
|
||||
let timeout = std::time::Duration::new(10, 0);
|
||||
let timeout = std::time::Duration::new(15, 0);
|
||||
|
||||
let file =
|
||||
proxmox_sys::fs::open_file_locked(&self.task_lock_fn, timeout, exclusive, options)?;
|
||||
@ -923,7 +923,13 @@ impl WorkerTask {
|
||||
set_worker_count(hash.len());
|
||||
}
|
||||
|
||||
setup.update_active_workers(Some(&upid))?;
|
||||
// this wants to access WORKER_TASK_LIST, so we need to drop the lock above
|
||||
let res = setup.update_active_workers(Some(&upid));
|
||||
if res.is_err() {
|
||||
// needed to undo the insertion into WORKER_TASK_LIST above
|
||||
worker.log_result(&res);
|
||||
res?
|
||||
}
|
||||
|
||||
Ok((worker, logger))
|
||||
}
|
||||
@ -1017,8 +1023,11 @@ impl WorkerTask {
|
||||
self.log_message(state.result_text());
|
||||
|
||||
WORKER_TASK_LIST.lock().unwrap().remove(&self.upid.task_id);
|
||||
// this wants to access WORKER_TASK_LIST, so we need to drop the lock above
|
||||
let _ = self.setup.update_active_workers(None);
|
||||
set_worker_count(WORKER_TASK_LIST.lock().unwrap().len());
|
||||
// re-acquire the lock and hold it while updating the count
|
||||
let lock = WORKER_TASK_LIST.lock().unwrap();
|
||||
set_worker_count(lock.len());
|
||||
}
|
||||
|
||||
/// Log a message.
|
||||
|
@ -1,10 +1,10 @@
|
||||
Source: rust-proxmox-router
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-anyhow-1+default-dev <!nocheck>,
|
||||
librust-env-logger-0.10+default-dev <!nocheck>,
|
||||
@ -25,9 +25,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-plain-1+default-dev <!nocheck>,
|
||||
librust-unicode-width-0.1+default-dev (>= 0.1.8-~~) <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-router
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-rrd-api-types
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -12,9 +12,10 @@ Build-Depends: debhelper (>= 12),
|
||||
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
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-rrd-api-types
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "proxmox-rrd"
|
||||
description = "Simple RRD database implementation."
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
@ -1,3 +1,9 @@
|
||||
rust-proxmox-rrd (0.4.1-1) bookworm; urgency=medium
|
||||
|
||||
* do not log three info-level messages on applying journal, one is enough
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 25 Nov 2024 17:48:55 +0100
|
||||
|
||||
rust-proxmox-rrd (0.4.0-1) bookworm; urgency=medium
|
||||
|
||||
* drop api-types feature, the module was moved into its own crate
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-rrd
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -21,9 +21,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-json-1+default-dev <!nocheck>,
|
||||
librust-serde-plain-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-rrd
|
||||
Rules-Requires-Root: no
|
||||
|
||||
@ -55,8 +56,8 @@ Provides:
|
||||
librust-proxmox-rrd-0.4-dev (= ${binary:Version}),
|
||||
librust-proxmox-rrd-0.4+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-rrd-0.4+rrd-v1-dev (= ${binary:Version}),
|
||||
librust-proxmox-rrd-0.4.0-dev (= ${binary:Version}),
|
||||
librust-proxmox-rrd-0.4.0+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-rrd-0.4.0+rrd-v1-dev (= ${binary:Version})
|
||||
librust-proxmox-rrd-0.4.1-dev (= ${binary:Version}),
|
||||
librust-proxmox-rrd-0.4.1+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-rrd-0.4.1+rrd-v1-dev (= ${binary:Version})
|
||||
Description: Simple RRD database implementation - Rust source code
|
||||
Source code for Debianized Rust crate "proxmox-rrd"
|
||||
|
@ -285,13 +285,9 @@ fn apply_and_commit_journal_thread(
|
||||
match apply_journal_impl(Arc::clone(&state), Arc::clone(&rrd_map)) {
|
||||
Ok(entries) => {
|
||||
let elapsed = start_time.elapsed().unwrap().as_secs_f64();
|
||||
log::info!(
|
||||
"applied rrd journal ({} entries in {:.3} seconds)",
|
||||
entries,
|
||||
elapsed
|
||||
);
|
||||
log::info!("applied rrd journal ({entries} entries in {elapsed:.3} seconds)");
|
||||
}
|
||||
Err(err) => bail!("apply rrd journal failed - {}", err),
|
||||
Err(err) => bail!("apply rrd journal failed - {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,12 +298,10 @@ fn apply_and_commit_journal_thread(
|
||||
Ok(rrd_file_count) => {
|
||||
let elapsed = start_time.elapsed().unwrap().as_secs_f64();
|
||||
log::info!(
|
||||
"rrd journal successfully committed ({} files in {:.3} seconds)",
|
||||
rrd_file_count,
|
||||
elapsed
|
||||
"rrd journal successfully committed ({rrd_file_count} files in {elapsed:.3} seconds)"
|
||||
);
|
||||
}
|
||||
Err(err) => bail!("rrd journal commit failed: {}", err),
|
||||
Err(err) => bail!("rrd journal commit failed: {err}"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -448,7 +442,7 @@ fn commit_journal_impl(
|
||||
|
||||
let mut dir_set = BTreeSet::new();
|
||||
|
||||
log::info!("write rrd data back to disk");
|
||||
log::debug!("write rrd data back to disk");
|
||||
|
||||
// save all RRDs - we only need a read lock here
|
||||
// Note: no fsync here (we do it afterwards)
|
||||
@ -470,7 +464,7 @@ fn commit_journal_impl(
|
||||
// the likelihood that files are already synced, so this is
|
||||
// much faster (although we need to re-open the files).
|
||||
|
||||
log::info!("starting rrd data sync");
|
||||
log::debug!("starting rrd data sync");
|
||||
|
||||
for rel_path in files.iter() {
|
||||
let mut path = config.basedir.clone();
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-schema
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -12,9 +12,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-json-1+default-dev <!nocheck>,
|
||||
librust-textwrap-0.16+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-schema
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-section-config
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -13,9 +13,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+default-dev <!nocheck>,
|
||||
librust-serde-json-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-section-config
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
20
proxmox-sendmail/Cargo.toml
Normal file
20
proxmox-sendmail/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "proxmox-sendmail"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
exclude.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
percent-encoding = { workspace = true }
|
||||
proxmox-time = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
mail-forwarder = []
|
5
proxmox-sendmail/debian/changelog
Normal file
5
proxmox-sendmail/debian/changelog
Normal file
@ -0,0 +1,5 @@
|
||||
rust-proxmox-sendmail (0.1.0-1) bookworm; urgency=medium
|
||||
|
||||
* Initial release.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 02 Dec 2024 14:47:42 +0100
|
43
proxmox-sendmail/debian/control
Normal file
43
proxmox-sendmail/debian/control
Normal file
@ -0,0 +1,43 @@
|
||||
Source: rust-proxmox-sendmail
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-anyhow-1+default-dev <!nocheck>,
|
||||
librust-base64-0.13+default-dev <!nocheck>,
|
||||
librust-percent-encoding-2+default-dev (>= 2.1-~~) <!nocheck>,
|
||||
librust-proxmox-time-2+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-sendmail
|
||||
Rules-Requires-Root: no
|
||||
|
||||
Package: librust-proxmox-sendmail-dev
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
librust-anyhow-1+default-dev,
|
||||
librust-base64-0.13+default-dev,
|
||||
librust-percent-encoding-2+default-dev (>= 2.1-~~),
|
||||
librust-proxmox-time-2+default-dev
|
||||
Provides:
|
||||
librust-proxmox-sendmail+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-sendmail+mail-forwarder-dev (= ${binary:Version}),
|
||||
librust-proxmox-sendmail-0-dev (= ${binary:Version}),
|
||||
librust-proxmox-sendmail-0+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-sendmail-0+mail-forwarder-dev (= ${binary:Version}),
|
||||
librust-proxmox-sendmail-0.1-dev (= ${binary:Version}),
|
||||
librust-proxmox-sendmail-0.1+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-sendmail-0.1+mail-forwarder-dev (= ${binary:Version}),
|
||||
librust-proxmox-sendmail-0.1.0-dev (= ${binary:Version}),
|
||||
librust-proxmox-sendmail-0.1.0+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-sendmail-0.1.0+mail-forwarder-dev (= ${binary:Version})
|
||||
Description: Rust crate "proxmox-sendmail" - Rust source code
|
||||
Source code for Debianized Rust crate "proxmox-sendmail"
|
18
proxmox-sendmail/debian/copyright
Normal file
18
proxmox-sendmail/debian/copyright
Normal file
@ -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/>.
|
7
proxmox-sendmail/debian/debcargo.toml
Normal file
7
proxmox-sendmail/debian/debcargo.toml
Normal file
@ -0,0 +1,7 @@
|
||||
overlay = "."
|
||||
crate_src_path = ".."
|
||||
maintainer = "Proxmox Support Team <support@proxmox.com>"
|
||||
|
||||
[source]
|
||||
vcs_git = "git://git.proxmox.com/git/proxmox.git"
|
||||
vcs_browser = "https://git.proxmox.com/?p=proxmox.git"
|
836
proxmox-sendmail/src/lib.rs
Normal file
836
proxmox-sendmail/src/lib.rs
Normal file
@ -0,0 +1,836 @@
|
||||
//!
|
||||
//! This library implements the [`Mail`] trait which makes it easy to send emails with attachments
|
||||
//! and alternative html parts to one or multiple receivers via ``sendmail``.
|
||||
//!
|
||||
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use anyhow::{bail, Context, Error};
|
||||
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
|
||||
|
||||
// Characters in this set will be encoded, so reproduce the inverse of the set described by RFC5987
|
||||
// Section 3.2.1 `attr-char`, as that describes all characters that **don't** need encoding:
|
||||
//
|
||||
// https://datatracker.ietf.org/doc/html/rfc5987#section-3.2.1
|
||||
//
|
||||
// `CONTROLS` contains all control characters 0x00 - 0x1f and 0x7f as well as all non-ascii
|
||||
// characters, so we need to add all characters here that aren't described in `attr-char` that are
|
||||
// in the range 0x20-0x7e
|
||||
const RFC5987SET: &AsciiSet = &CONTROLS
|
||||
.add(b' ')
|
||||
.add(b'"')
|
||||
.add(b'%')
|
||||
.add(b'&')
|
||||
.add(b'\'')
|
||||
.add(b'(')
|
||||
.add(b')')
|
||||
.add(b'*')
|
||||
.add(b',')
|
||||
.add(b'/')
|
||||
.add(b':')
|
||||
.add(b';')
|
||||
.add(b'<')
|
||||
.add(b'=')
|
||||
.add(b'>')
|
||||
.add(b'?')
|
||||
.add(b'@')
|
||||
.add(b'[')
|
||||
.add(b'\\')
|
||||
.add(b']')
|
||||
.add(b'{')
|
||||
.add(b'}');
|
||||
|
||||
struct Recipient {
|
||||
name: Option<String>,
|
||||
email: String,
|
||||
}
|
||||
|
||||
impl Recipient {
|
||||
// Returns true if the name of the recipient is undefined or contains only ascii characters
|
||||
fn is_ascii(&self) -> bool {
|
||||
self.name.as_ref().map(|n| n.is_ascii()).unwrap_or(true)
|
||||
}
|
||||
|
||||
fn format_recipient(&self) -> String {
|
||||
if let Some(name) = &self.name {
|
||||
if !name.is_ascii() {
|
||||
format!("=?utf-8?B?{}?= <{}>", base64::encode(name), self.email)
|
||||
} else {
|
||||
format!("{name} <{}>", self.email)
|
||||
}
|
||||
} else {
|
||||
self.email.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Attachment<'a> {
|
||||
filename: String,
|
||||
mime: String,
|
||||
content: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> Attachment<'a> {
|
||||
fn format_attachment(&self, file_boundary: &str) -> String {
|
||||
use std::fmt::Write;
|
||||
|
||||
let mut attachment = String::new();
|
||||
|
||||
let _ = writeln!(attachment, "\n--{file_boundary}");
|
||||
let _ = writeln!(
|
||||
attachment,
|
||||
"Content-Type: {}; name=\"{}\"",
|
||||
self.mime, self.filename
|
||||
);
|
||||
|
||||
// both `filename` and `filename*` are included for additional compatability
|
||||
let _ = writeln!(
|
||||
attachment,
|
||||
"Content-Disposition: attachment; filename=\"{}\"; filename*=UTF-8''{}",
|
||||
self.filename,
|
||||
utf8_percent_encode(&self.filename, RFC5987SET)
|
||||
);
|
||||
attachment.push_str("Content-Transfer-Encoding: base64\n\n");
|
||||
|
||||
// base64 encode the attachment and hard-wrap the base64 encoded string every 72
|
||||
// characters. this improves compatability.
|
||||
attachment.push_str(
|
||||
&base64::encode(self.content)
|
||||
.chars()
|
||||
.enumerate()
|
||||
.flat_map(|(i, c)| {
|
||||
if i != 0 && i % 72 == 0 {
|
||||
Some('\n')
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.into_iter()
|
||||
.chain(std::iter::once(c))
|
||||
})
|
||||
.collect::<String>(),
|
||||
);
|
||||
|
||||
attachment
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct is used to define mails that are to be sent via the `sendmail` command.
|
||||
pub struct Mail<'a> {
|
||||
mail_author: String,
|
||||
mail_from: String,
|
||||
subject: String,
|
||||
to: Vec<Recipient>,
|
||||
body_txt: String,
|
||||
body_html: Option<String>,
|
||||
attachments: Vec<Attachment<'a>>,
|
||||
mask_participants: bool,
|
||||
}
|
||||
|
||||
impl<'a> Mail<'a> {
|
||||
/// Creates a new mail with a mail author, from address, subject line and a plain text body.
|
||||
///
|
||||
/// Note: If the author's name or the subject line contains UTF-8 characters they will be
|
||||
/// appropriately encoded.
|
||||
pub fn new(mail_author: &str, mail_from: &str, subject: &str, body_txt: &str) -> Self {
|
||||
Self {
|
||||
mail_author: mail_author.to_string(),
|
||||
mail_from: mail_from.to_string(),
|
||||
subject: subject.to_string(),
|
||||
to: Vec::new(),
|
||||
body_txt: body_txt.to_string(),
|
||||
body_html: None,
|
||||
attachments: Vec::new(),
|
||||
mask_participants: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a recipient to the mail without specifying a name separately.
|
||||
///
|
||||
/// Note: No formatting or encoding will be done here, the value will be passed to the `To:`
|
||||
/// header directly.
|
||||
pub fn add_recipient(&mut self, email: &str) {
|
||||
self.to.push(Recipient {
|
||||
name: None,
|
||||
email: email.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
/// Builder-pattern method to conveniently add a recipient to an email without specifying a
|
||||
/// name separately.
|
||||
///
|
||||
/// Note: No formatting or encoding will be done here, the value will be passed to the `To:`
|
||||
/// header directly.
|
||||
pub fn with_recipient(mut self, email: &str) -> Self {
|
||||
self.add_recipient(email);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a recipient to the mail with a name.
|
||||
///
|
||||
/// Notes:
|
||||
///
|
||||
/// - If the name contains UTF-8 characters it will be encoded. Then the possibly encoded name
|
||||
/// and non-encoded email address will be passed to the `To:` header in this format:
|
||||
/// `{encoded_name} <{email}>`
|
||||
/// - If multiple receivers are specified, they will be masked so as not to disclose them to
|
||||
/// other receivers. This can be disabled via [`Mail::unmask_recipients`] or
|
||||
/// [`Mail::with_unmasked_recipients`].
|
||||
pub fn add_recipient_and_name(&mut self, name: &str, email: &str) {
|
||||
self.to.push(Recipient {
|
||||
name: Some(name.to_string()),
|
||||
email: email.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
/// Builder-style method to conveniently add a recipient with a name to an email.
|
||||
///
|
||||
/// Notes:
|
||||
///
|
||||
/// - If the name contains UTF-8 characters it will be encoded. Then the possibly encoded name
|
||||
/// and non-encoded email address will be passed to the `To:` header in this format:
|
||||
/// `{encoded_name} <{email}>`
|
||||
/// - If multiple receivers are specified, they will be masked so as not to disclose them to
|
||||
/// other receivers. This can be disabled via [`Mail::unmask_recipients`] or
|
||||
/// [`Mail::with_unmasked_recipients`].
|
||||
pub fn with_recipient_and_name(mut self, name: &str, email: &str) -> Self {
|
||||
self.add_recipient_and_name(name, email);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an attachment with a specified file name and mime-type to an email.
|
||||
///
|
||||
/// Note: Adding attachments triggers `multipart/mixed` mode.
|
||||
pub fn add_attachment(&mut self, filename: &str, mime_type: &str, content: &'a [u8]) {
|
||||
self.attachments.push(Attachment {
|
||||
filename: filename.to_string(),
|
||||
mime: mime_type.to_string(),
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
||||
/// Builder-style method to conveniently add an attachment with a specific filename and
|
||||
/// mime-type to an email.
|
||||
///
|
||||
/// Note: Adding attachements triggers `multipart/mixed` mode.
|
||||
pub fn with_attachment(mut self, filename: &str, mime_type: &str, content: &'a [u8]) -> Self {
|
||||
self.add_attachment(filename, mime_type, content);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set an alternative HTML part.
|
||||
///
|
||||
/// Note: This triggers `multipart/alternative` mode. If both an HTML part and at least one
|
||||
/// attachment are specified, the `multipart/alternative` part will be nested within the first
|
||||
/// `multipart/mixed` part. This should ensure that the HTML is displayed properly by client's
|
||||
/// that prioritize it over the plain text part (should be the default for most clients) while
|
||||
/// also properly displaying the attachments.
|
||||
pub fn set_html_alt(&mut self, body_html: &str) {
|
||||
self.body_html.replace(body_html.to_string());
|
||||
}
|
||||
|
||||
/// Builder-style method to add an alternative HTML part.
|
||||
///
|
||||
/// Note: This triggers `multipart/alternative` mode. If both an HTML part and at least one
|
||||
/// attachment are specified, the `multipart/alternative` part will be nested within the first
|
||||
/// `multipart/mixed` part. This should ensure that the HTML is displayed properly by client's
|
||||
/// that prioritize it over the plain text part (should be the default for most clients) while
|
||||
/// also properly displaying the attachments.
|
||||
pub fn with_html_alt(mut self, body_html: &str) -> Self {
|
||||
self.set_html_alt(body_html);
|
||||
self
|
||||
}
|
||||
|
||||
/// This function ensures that recipients of the mail are not masked. Being able to see all
|
||||
/// recipients of a mail can be helpful in, for example, notification scenarios.
|
||||
pub fn unmask_recipients(&mut self) {
|
||||
self.mask_participants = false;
|
||||
}
|
||||
|
||||
/// Builder-style function that ensures that recipients of the mail are not masked. Being able
|
||||
/// to see all recipients of a mail can be helpful in, for example, notification scenarios.
|
||||
pub fn with_unmasked_recipients(mut self) -> Self {
|
||||
self.unmask_recipients();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sends the email. This will fail if no recipients have been added.
|
||||
///
|
||||
/// Note: An `Auto-Submitted: auto-generated` header is added to avoid triggering OOO and
|
||||
/// similar mails.
|
||||
pub fn send(&self) -> Result<(), Error> {
|
||||
if self.to.is_empty() {
|
||||
bail!("no recipients provided for the mail, cannot send it.");
|
||||
}
|
||||
|
||||
let now = proxmox_time::epoch_i64();
|
||||
let body = self.format_mail(now)?;
|
||||
|
||||
let mut sendmail_process = Command::new("/usr/sbin/sendmail")
|
||||
.arg("-B")
|
||||
.arg("8BITMIME")
|
||||
.arg("-f")
|
||||
.arg(&self.mail_from)
|
||||
.arg("--")
|
||||
.args(self.to.iter().map(|p| &p.email).collect::<Vec<&String>>())
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()
|
||||
.with_context(|| "could not spawn sendmail process")?;
|
||||
|
||||
sendmail_process
|
||||
.stdin
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.write_all(body.as_bytes())
|
||||
.with_context(|| "couldn't write to sendmail stdin")?;
|
||||
|
||||
sendmail_process
|
||||
.wait()
|
||||
.with_context(|| "sendmail did not exit successfully")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Forwards an email message to a given list of recipients.
|
||||
///
|
||||
/// `message` must be compatible with ``sendmail`` (the message is piped into stdin unmodified).
|
||||
#[cfg(feature = "mail-forwarder")]
|
||||
pub fn forward(
|
||||
mailto: &[&str],
|
||||
mailfrom: &str,
|
||||
message: &[u8],
|
||||
uid: Option<u32>,
|
||||
) -> Result<(), Error> {
|
||||
use std::os::unix::process::CommandExt;
|
||||
|
||||
if mailto.is_empty() {
|
||||
bail!("At least one recipient has to be specified!");
|
||||
}
|
||||
|
||||
let mut builder = Command::new("/usr/sbin/sendmail");
|
||||
|
||||
builder
|
||||
.args([
|
||||
"-N", "never", // never send DSN (avoid mail loops)
|
||||
"-f", mailfrom, "--",
|
||||
])
|
||||
.args(mailto)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
|
||||
if let Some(uid) = uid {
|
||||
builder.uid(uid);
|
||||
}
|
||||
|
||||
let mut sendmail_process = builder
|
||||
.spawn()
|
||||
.with_context(|| "could not spawn sendmail process")?;
|
||||
|
||||
sendmail_process
|
||||
.stdin
|
||||
.take()
|
||||
.unwrap()
|
||||
.write_all(message)
|
||||
.with_context(|| "couldn't write to sendmail stdin")?;
|
||||
|
||||
sendmail_process
|
||||
.wait()
|
||||
.with_context(|| "sendmail did not exit successfully")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_mail(&self, now: i64) -> Result<String, Error> {
|
||||
use std::fmt::Write;
|
||||
|
||||
let file_boundary = format!("----_=_NextPart_001_{now}");
|
||||
let html_boundary = format!("----_=_NextPart_002_{now}");
|
||||
|
||||
let mut mail = self.format_header(now, &file_boundary, &html_boundary)?;
|
||||
mail.push_str(&self.format_body(&file_boundary, &html_boundary)?);
|
||||
|
||||
if !self.attachments.is_empty() {
|
||||
mail.push_str(
|
||||
&self
|
||||
.attachments
|
||||
.iter()
|
||||
.map(|a| a.format_attachment(&file_boundary))
|
||||
.collect::<String>(),
|
||||
);
|
||||
|
||||
write!(mail, "\n--{file_boundary}--")?;
|
||||
}
|
||||
|
||||
Ok(mail)
|
||||
}
|
||||
|
||||
fn format_header(
|
||||
&self,
|
||||
now: i64,
|
||||
file_boundary: &str,
|
||||
html_boundary: &str,
|
||||
) -> Result<String, Error> {
|
||||
use std::fmt::Write;
|
||||
|
||||
let mut header = String::new();
|
||||
|
||||
let encoded_to = if self.to.len() > 1 && self.mask_participants {
|
||||
// if the receivers are masked, we know that they don't need to be encoded
|
||||
false
|
||||
} else {
|
||||
// check if there is a recipient that needs encoding
|
||||
self.to.iter().any(|r| !r.is_ascii())
|
||||
};
|
||||
|
||||
if !self.attachments.is_empty() {
|
||||
header.push_str("Content-Type: multipart/mixed;\n");
|
||||
writeln!(header, "\tboundary=\"{file_boundary}\"")?;
|
||||
header.push_str("MIME-Version: 1.0\n");
|
||||
} else if self.body_html.is_some() {
|
||||
header.push_str("Content-Type: multipart/alternative;\n");
|
||||
writeln!(header, "\tboundary=\"{html_boundary}\"")?;
|
||||
header.push_str("MIME-Version: 1.0\n");
|
||||
} else if !self.subject.is_ascii() || !self.mail_author.is_ascii() || encoded_to {
|
||||
header.push_str("MIME-Version: 1.0\n");
|
||||
}
|
||||
|
||||
if !self.subject.is_ascii() {
|
||||
writeln!(
|
||||
header,
|
||||
"Subject: =?utf-8?B?{}?=",
|
||||
base64::encode(&self.subject)
|
||||
)?;
|
||||
} else {
|
||||
writeln!(header, "Subject: {}", self.subject)?;
|
||||
};
|
||||
|
||||
if !self.mail_author.is_ascii() {
|
||||
writeln!(
|
||||
header,
|
||||
"From: =?utf-8?B?{}?= <{}>",
|
||||
base64::encode(&self.mail_author),
|
||||
self.mail_from
|
||||
)?;
|
||||
} else {
|
||||
writeln!(header, "From: {} <{}>", self.mail_author, self.mail_from)?;
|
||||
}
|
||||
|
||||
let to = if self.to.len() > 1 && self.mask_participants {
|
||||
// don't disclose all recipients if the mail goes out to multiple
|
||||
let recipient = Recipient {
|
||||
name: Some("Undisclosed".to_string()),
|
||||
email: "noreply".to_string(),
|
||||
};
|
||||
|
||||
recipient.format_recipient()
|
||||
} else {
|
||||
self.to
|
||||
.iter()
|
||||
.map(Recipient::format_recipient)
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
};
|
||||
|
||||
writeln!(header, "To: {to}")?;
|
||||
|
||||
let rfc2822_date = proxmox_time::epoch_to_rfc2822(now)
|
||||
.with_context(|| "could not convert epoch to rfc2822 date")?;
|
||||
writeln!(header, "Date: {rfc2822_date}")?;
|
||||
header.push_str("Auto-Submitted: auto-generated;\n");
|
||||
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
fn format_body(&self, file_boundary: &str, html_boundary: &str) -> Result<String, Error> {
|
||||
use std::fmt::Write;
|
||||
|
||||
let mut body = String::new();
|
||||
|
||||
if self.body_html.is_some() && !self.attachments.is_empty() {
|
||||
body.push_str("\nThis is a multi-part message in MIME format.\n");
|
||||
writeln!(body, "\n--{file_boundary}")?;
|
||||
writeln!(
|
||||
body,
|
||||
"Content-Type: multipart/alternative; boundary=\"{html_boundary}\""
|
||||
)?;
|
||||
body.push_str("MIME-Version: 1.0\n");
|
||||
writeln!(body, "\n--{html_boundary}")?;
|
||||
} else if self.body_html.is_some() {
|
||||
body.push_str("\nThis is a multi-part message in MIME format.\n");
|
||||
writeln!(body, "\n--{html_boundary}")?;
|
||||
} else if self.body_html.is_none() && !self.attachments.is_empty() {
|
||||
body.push_str("\nThis is a multi-part message in MIME format.\n");
|
||||
writeln!(body, "\n--{file_boundary}")?;
|
||||
}
|
||||
|
||||
body.push_str("Content-Type: text/plain;\n");
|
||||
body.push_str("\tcharset=\"UTF-8\"\n");
|
||||
body.push_str("Content-Transfer-Encoding: 8bit\n\n");
|
||||
body.push_str(&self.body_txt);
|
||||
|
||||
if let Some(html) = &self.body_html {
|
||||
writeln!(body, "\n--{html_boundary}")?;
|
||||
body.push_str("Content-Type: text/html;\n");
|
||||
body.push_str("\tcharset=\"UTF-8\"\n");
|
||||
body.push_str("Content-Transfer-Encoding: 8bit\n\n");
|
||||
body.push_str(html);
|
||||
write!(body, "\n--{html_boundary}--")?;
|
||||
}
|
||||
|
||||
Ok(body)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn email_without_recipients_fails() {
|
||||
let result = Mail::new("Sender", "mail@example.com", "hi", "body").send();
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "mail-forwarder")]
|
||||
fn forwarding_without_recipients_fails() {
|
||||
let result = Mail::forward(&[], "me@example.com", String::from("text").as_bytes(), None);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_ascii_text_mail() {
|
||||
let mail = Mail::new(
|
||||
"Sender Name",
|
||||
"mailfrom@example.com",
|
||||
"Subject Line",
|
||||
"This is just ascii text.\nNothing too special.",
|
||||
)
|
||||
.with_recipient_and_name("Receiver Name", "receiver@example.com");
|
||||
|
||||
let body = mail.format_mail(0).expect("could not format mail");
|
||||
|
||||
assert_eq!(
|
||||
body,
|
||||
r#"Subject: Subject Line
|
||||
From: Sender Name <mailfrom@example.com>
|
||||
To: Receiver Name <receiver@example.com>
|
||||
Date: Thu, 01 Jan 1970 01:00:00 +0100
|
||||
Auto-Submitted: auto-generated;
|
||||
Content-Type: text/plain;
|
||||
charset="UTF-8"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
This is just ascii text.
|
||||
Nothing too special."#
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_receiver_masked() {
|
||||
let mail = Mail::new(
|
||||
"Sender Name",
|
||||
"mailfrom@example.com",
|
||||
"Subject Line",
|
||||
"This is just ascii text.\nNothing too special.",
|
||||
)
|
||||
.with_recipient_and_name("Receiver Name", "receiver@example.com")
|
||||
.with_recipient("two@example.com")
|
||||
.with_recipient_and_name("mäx müstermänn", "mm@example.com");
|
||||
|
||||
let body = mail.format_mail(0).expect("could not format mail");
|
||||
|
||||
assert_eq!(
|
||||
body,
|
||||
r#"Subject: Subject Line
|
||||
From: Sender Name <mailfrom@example.com>
|
||||
To: Undisclosed <noreply>
|
||||
Date: Thu, 01 Jan 1970 01:00:00 +0100
|
||||
Auto-Submitted: auto-generated;
|
||||
Content-Type: text/plain;
|
||||
charset="UTF-8"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
This is just ascii text.
|
||||
Nothing too special."#
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_receiver_unmasked() {
|
||||
let mail = Mail::new(
|
||||
"Sender Name",
|
||||
"mailfrom@example.com",
|
||||
"Subject Line",
|
||||
"This is just ascii text.\nNothing too special.",
|
||||
)
|
||||
.with_recipient_and_name("Receiver Name", "receiver@example.com")
|
||||
.with_recipient("two@example.com")
|
||||
.with_recipient_and_name("mäx müstermänn", "mm@example.com")
|
||||
.with_unmasked_recipients();
|
||||
|
||||
let body = mail.format_mail(0).expect("could not format mail");
|
||||
|
||||
assert_eq!(
|
||||
body,
|
||||
r#"MIME-Version: 1.0
|
||||
Subject: Subject Line
|
||||
From: Sender Name <mailfrom@example.com>
|
||||
To: Receiver Name <receiver@example.com>, two@example.com, =?utf-8?B?bcOkeCBtw7xzdGVybcOkbm4=?= <mm@example.com>
|
||||
Date: Thu, 01 Jan 1970 01:00:00 +0100
|
||||
Auto-Submitted: auto-generated;
|
||||
Content-Type: text/plain;
|
||||
charset="UTF-8"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
This is just ascii text.
|
||||
Nothing too special."#
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_utf8_text_mail() {
|
||||
let mail = Mail::new(
|
||||
"UTF-8 Sender Name 📧",
|
||||
"differentfrom@example.com",
|
||||
"Subject Line 🧑",
|
||||
"This utf-8 email should handle emojis\n🧑📧\nand weird german characters: öäüß\nand more.",
|
||||
)
|
||||
.with_recipient_and_name("Receiver Name📩", "receiver@example.com");
|
||||
|
||||
let body = mail.format_mail(1732806251).expect("could not format mail");
|
||||
|
||||
assert_eq!(
|
||||
body,
|
||||
r#"MIME-Version: 1.0
|
||||
Subject: =?utf-8?B?U3ViamVjdCBMaW5lIPCfp5E=?=
|
||||
From: =?utf-8?B?VVRGLTggU2VuZGVyIE5hbWUg8J+Tpw==?= <differentfrom@example.com>
|
||||
To: =?utf-8?B?UmVjZWl2ZXIgTmFtZfCfk6k=?= <receiver@example.com>
|
||||
Date: Thu, 28 Nov 2024 16:04:11 +0100
|
||||
Auto-Submitted: auto-generated;
|
||||
Content-Type: text/plain;
|
||||
charset="UTF-8"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
This utf-8 email should handle emojis
|
||||
🧑📧
|
||||
and weird german characters: öäüß
|
||||
and more."#
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multipart_html_alternative() {
|
||||
let mail = Mail::new(
|
||||
"Sender Name",
|
||||
"from@example.com",
|
||||
"Subject Line",
|
||||
"Lorem Ipsum Dolor Sit\nAmet",
|
||||
)
|
||||
.with_recipient("receiver@example.com")
|
||||
.with_html_alt("<html lang=\"de-at\"><head></head><body>\n\t<pre>\n\t\tLorem Ipsum Dolor Sit Amet\n\t</pre>\n</body></html>");
|
||||
let body = mail.format_mail(1732806251).expect("could not format mail");
|
||||
assert_eq!(
|
||||
body,
|
||||
r#"Content-Type: multipart/alternative;
|
||||
boundary="----_=_NextPart_002_1732806251"
|
||||
MIME-Version: 1.0
|
||||
Subject: Subject Line
|
||||
From: Sender Name <from@example.com>
|
||||
To: receiver@example.com
|
||||
Date: Thu, 28 Nov 2024 16:04:11 +0100
|
||||
Auto-Submitted: auto-generated;
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
|
||||
------_=_NextPart_002_1732806251
|
||||
Content-Type: text/plain;
|
||||
charset="UTF-8"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Lorem Ipsum Dolor Sit
|
||||
Amet
|
||||
------_=_NextPart_002_1732806251
|
||||
Content-Type: text/html;
|
||||
charset="UTF-8"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
<html lang="de-at"><head></head><body>
|
||||
<pre>
|
||||
Lorem Ipsum Dolor Sit Amet
|
||||
</pre>
|
||||
</body></html>
|
||||
------_=_NextPart_002_1732806251--"#
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multipart_plain_text_attachments_mixed() {
|
||||
let bin: [u8; 62] = [
|
||||
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad,
|
||||
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad,
|
||||
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad,
|
||||
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad,
|
||||
0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
|
||||
];
|
||||
|
||||
let mail = Mail::new(
|
||||
"Sender Name",
|
||||
"from@example.com",
|
||||
"Subject Line",
|
||||
"Lorem Ipsum Dolor Sit\nAmet",
|
||||
)
|
||||
.with_recipient_and_name("Receiver Name", "receiver@example.com")
|
||||
.with_attachment("deadbeef.bin", "application/octet-stream", &bin);
|
||||
|
||||
let body = mail.format_mail(1732806251).expect("could not format mail");
|
||||
assert_eq!(
|
||||
body,
|
||||
r#"Content-Type: multipart/mixed;
|
||||
boundary="----_=_NextPart_001_1732806251"
|
||||
MIME-Version: 1.0
|
||||
Subject: Subject Line
|
||||
From: Sender Name <from@example.com>
|
||||
To: Receiver Name <receiver@example.com>
|
||||
Date: Thu, 28 Nov 2024 16:04:11 +0100
|
||||
Auto-Submitted: auto-generated;
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
|
||||
------_=_NextPart_001_1732806251
|
||||
Content-Type: text/plain;
|
||||
charset="UTF-8"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Lorem Ipsum Dolor Sit
|
||||
Amet
|
||||
------_=_NextPart_001_1732806251
|
||||
Content-Type: application/octet-stream; name="deadbeef.bin"
|
||||
Content-Disposition: attachment; filename="deadbeef.bin"; filename*=UTF-8''deadbeef.bin
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
3q2+796tvu/erb7v3q3erb7v3q2+796tvu/erd6tvu/erb7v3q2+796t3q2+796tvu/erb7v
|
||||
3q2+796tvu8=
|
||||
------_=_NextPart_001_1732806251--"#
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multipart_plain_text_html_alternative_attachments() {
|
||||
let bin: [u8; 62] = [
|
||||
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad,
|
||||
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad,
|
||||
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad,
|
||||
0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad,
|
||||
0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
|
||||
];
|
||||
|
||||
let mail = Mail::new(
|
||||
"Sender Name",
|
||||
"from@example.com",
|
||||
"Subject Line",
|
||||
"Lorem Ipsum Dolor Sit\nAmet",
|
||||
)
|
||||
.with_recipient_and_name("Receiver Name", "receiver@example.com")
|
||||
.with_attachment("deadbeef.bin", "application/octet-stream", &bin)
|
||||
.with_attachment("🐄💀.bin", "image/bmp", &bin)
|
||||
.with_html_alt("<html lang=\"de-at\"><head></head><body>\n\t<pre>\n\t\tLorem Ipsum Dolor Sit Amet\n\t</pre>\n</body></html>");
|
||||
|
||||
let body = mail.format_mail(1732806251).expect("could not format mail");
|
||||
|
||||
assert_eq!(
|
||||
body,
|
||||
r#"Content-Type: multipart/mixed;
|
||||
boundary="----_=_NextPart_001_1732806251"
|
||||
MIME-Version: 1.0
|
||||
Subject: Subject Line
|
||||
From: Sender Name <from@example.com>
|
||||
To: Receiver Name <receiver@example.com>
|
||||
Date: Thu, 28 Nov 2024 16:04:11 +0100
|
||||
Auto-Submitted: auto-generated;
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
|
||||
------_=_NextPart_001_1732806251
|
||||
Content-Type: multipart/alternative; boundary="----_=_NextPart_002_1732806251"
|
||||
MIME-Version: 1.0
|
||||
|
||||
------_=_NextPart_002_1732806251
|
||||
Content-Type: text/plain;
|
||||
charset="UTF-8"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Lorem Ipsum Dolor Sit
|
||||
Amet
|
||||
------_=_NextPart_002_1732806251
|
||||
Content-Type: text/html;
|
||||
charset="UTF-8"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
<html lang="de-at"><head></head><body>
|
||||
<pre>
|
||||
Lorem Ipsum Dolor Sit Amet
|
||||
</pre>
|
||||
</body></html>
|
||||
------_=_NextPart_002_1732806251--
|
||||
------_=_NextPart_001_1732806251
|
||||
Content-Type: application/octet-stream; name="deadbeef.bin"
|
||||
Content-Disposition: attachment; filename="deadbeef.bin"; filename*=UTF-8''deadbeef.bin
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
3q2+796tvu/erb7v3q3erb7v3q2+796tvu/erd6tvu/erb7v3q2+796t3q2+796tvu/erb7v
|
||||
3q2+796tvu8=
|
||||
------_=_NextPart_001_1732806251
|
||||
Content-Type: image/bmp; name="🐄💀.bin"
|
||||
Content-Disposition: attachment; filename="🐄💀.bin"; filename*=UTF-8''%F0%9F%90%84%F0%9F%92%80.bin
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
3q2+796tvu/erb7v3q3erb7v3q2+796tvu/erd6tvu/erb7v3q2+796t3q2+796tvu/erb7v
|
||||
3q2+796tvu8=
|
||||
------_=_NextPart_001_1732806251--"#
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_mail_multipart() {
|
||||
let mail = Mail::new(
|
||||
"Fred Oobar",
|
||||
"foobar@example.com",
|
||||
"This is the subject",
|
||||
"This is the plain body",
|
||||
)
|
||||
.with_recipient_and_name("Tony Est", "test@example.com")
|
||||
.with_html_alt("<body>This is the HTML body</body>");
|
||||
|
||||
let body = mail.format_mail(1718977850).expect("could not format mail");
|
||||
|
||||
assert_eq!(
|
||||
body,
|
||||
r#"Content-Type: multipart/alternative;
|
||||
boundary="----_=_NextPart_002_1718977850"
|
||||
MIME-Version: 1.0
|
||||
Subject: This is the subject
|
||||
From: Fred Oobar <foobar@example.com>
|
||||
To: Tony Est <test@example.com>
|
||||
Date: Fri, 21 Jun 2024 15:50:50 +0200
|
||||
Auto-Submitted: auto-generated;
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
|
||||
------_=_NextPart_002_1718977850
|
||||
Content-Type: text/plain;
|
||||
charset="UTF-8"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
This is the plain body
|
||||
------_=_NextPart_002_1718977850
|
||||
Content-Type: text/html;
|
||||
charset="UTF-8"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
<body>This is the HTML body</body>
|
||||
------_=_NextPart_002_1718977850--"#
|
||||
);
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-serde
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -13,9 +13,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+derive-dev <!nocheck>,
|
||||
uuid-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-serde
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-shared-cache
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -15,9 +15,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-json-1+default-dev <!nocheck>,
|
||||
librust-serde-json-1+raw-value-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: https://salsa.debian.org/rust-team/debcargo-conf.git [src/proxmox-shared-cache]
|
||||
Vcs-Browser: https://salsa.debian.org/rust-team/debcargo-conf/tree/master/src/proxmox-shared-cache
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-shared-cache
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-shared-memory
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -11,9 +11,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-nix-0.26+default-dev (>= 0.26.1-~~) <!nocheck>,
|
||||
librust-proxmox-sys-0.6+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-shared-memory
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-simple-config
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -14,9 +14,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+derive-dev <!nocheck>,
|
||||
librust-serde-json-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-simple-config
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-sortable-macro
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -12,9 +12,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-syn-2+full-dev <!nocheck>,
|
||||
librust-syn-2+visit-mut-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.1
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-sortable-macro
|
||||
Rules-Requires-Root: no
|
||||
|
||||
@ -37,5 +38,4 @@ Provides:
|
||||
librust-proxmox-sortable-macro-0.1.3-dev (= ${binary:Version}),
|
||||
librust-proxmox-sortable-macro-0.1.3+default-dev (= ${binary:Version})
|
||||
Description: Proxmox sortable macro - Rust source code
|
||||
This package contains the source for the Rust proxmox-sortable-macro crate,
|
||||
packaged by debcargo for use with cargo and dh-cargo.
|
||||
Source code for Debianized Rust crate "proxmox-sortable-macro"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "proxmox-subscription"
|
||||
description = "Proxmox subscription utilitites"
|
||||
version = "0.4.6"
|
||||
version = "0.5.0"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
@ -13,20 +13,21 @@ rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
base64.workspace = true
|
||||
hex.workspace = true
|
||||
openssl.workspace = true
|
||||
base64 = { workspace = true, optional = true }
|
||||
hex = { workspace = true, optional = true }
|
||||
openssl = { workspace = true, optional = true }
|
||||
regex.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
proxmox-http = { workspace = true, features = ["client-trait", "http-helpers"] }
|
||||
proxmox-http = { workspace = true, optional = true, features = ["client-trait", "http-helpers"] }
|
||||
proxmox-serde.workspace = true
|
||||
proxmox-sys.workspace = true
|
||||
proxmox-time.workspace = true
|
||||
proxmox-sys = { workspace = true, optional = true }
|
||||
proxmox-time = { workspace = true, optional = true }
|
||||
|
||||
proxmox-schema = { workspace = true, features = ["api-macro"], optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["impl"]
|
||||
impl = [ "dep:base64", "dep:hex", "dep:openssl", "dep:proxmox-http", "dep:proxmox-sys", "dep:proxmox-time"]
|
||||
api-types = ["dep:proxmox-schema"]
|
||||
|
@ -1,3 +1,9 @@
|
||||
rust-proxmox-subscription (0.5.0-1) bookworm; urgency=medium
|
||||
|
||||
* move most of the implmentation into `impl` feature
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 07 Nov 2024 12:31:46 +0100
|
||||
|
||||
rust-proxmox-subscription (0.4.6-1) bookworm; urgency=medium
|
||||
|
||||
* replace lazy_static with std's LazyLock and drop the dependency
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-subscription
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -10,9 +10,9 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-base64-0.13+default-dev <!nocheck>,
|
||||
librust-hex-0.4+default-dev <!nocheck>,
|
||||
librust-openssl-0.10+default-dev <!nocheck>,
|
||||
librust-proxmox-http-0.9+client-trait-dev (>= 0.9.2-~~) <!nocheck>,
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.2-~~) <!nocheck>,
|
||||
librust-proxmox-http-0.9+http-helpers-dev (>= 0.9.2-~~) <!nocheck>,
|
||||
librust-proxmox-http-0.9+client-trait-dev (>= 0.9.4-~~) <!nocheck>,
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.4-~~) <!nocheck>,
|
||||
librust-proxmox-http-0.9+http-helpers-dev (>= 0.9.4-~~) <!nocheck>,
|
||||
librust-proxmox-serde-0.1+default-dev (>= 0.1.1-~~) <!nocheck>,
|
||||
librust-proxmox-serde-0.1+serde-json-dev (>= 0.1.1-~~) <!nocheck>,
|
||||
librust-proxmox-sys-0.6+default-dev <!nocheck>,
|
||||
@ -21,9 +21,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+default-dev <!nocheck>,
|
||||
librust-serde-json-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-subscription
|
||||
Rules-Requires-Root: no
|
||||
|
||||
@ -33,29 +34,19 @@ Multi-Arch: same
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
librust-anyhow-1+default-dev,
|
||||
librust-base64-0.13+default-dev,
|
||||
librust-hex-0.4+default-dev,
|
||||
librust-openssl-0.10+default-dev,
|
||||
librust-proxmox-http-0.9+client-trait-dev (>= 0.9.2-~~),
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.2-~~),
|
||||
librust-proxmox-http-0.9+http-helpers-dev (>= 0.9.2-~~),
|
||||
librust-proxmox-serde-0.1+default-dev (>= 0.1.1-~~),
|
||||
librust-proxmox-serde-0.1+serde-json-dev (>= 0.1.1-~~),
|
||||
librust-proxmox-sys-0.6+default-dev,
|
||||
librust-proxmox-time-2+default-dev,
|
||||
librust-regex-1+default-dev (>= 1.5-~~),
|
||||
librust-serde-1+default-dev,
|
||||
librust-serde-json-1+default-dev
|
||||
Recommends:
|
||||
librust-proxmox-subscription+impl-dev (= ${binary:Version})
|
||||
Suggests:
|
||||
librust-proxmox-subscription+api-types-dev (= ${binary:Version})
|
||||
Provides:
|
||||
librust-proxmox-subscription+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-subscription-0-dev (= ${binary:Version}),
|
||||
librust-proxmox-subscription-0+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-subscription-0.4-dev (= ${binary:Version}),
|
||||
librust-proxmox-subscription-0.4+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-subscription-0.4.6-dev (= ${binary:Version}),
|
||||
librust-proxmox-subscription-0.4.6+default-dev (= ${binary:Version})
|
||||
librust-proxmox-subscription-0.5-dev (= ${binary:Version}),
|
||||
librust-proxmox-subscription-0.5.0-dev (= ${binary:Version})
|
||||
Description: Proxmox subscription utilitites - Rust source code
|
||||
Source code for Debianized Rust crate "proxmox-subscription"
|
||||
|
||||
@ -69,8 +60,36 @@ Depends:
|
||||
librust-proxmox-schema-3+default-dev (>= 3.1.2-~~)
|
||||
Provides:
|
||||
librust-proxmox-subscription-0+api-types-dev (= ${binary:Version}),
|
||||
librust-proxmox-subscription-0.4+api-types-dev (= ${binary:Version}),
|
||||
librust-proxmox-subscription-0.4.6+api-types-dev (= ${binary:Version})
|
||||
librust-proxmox-subscription-0.5+api-types-dev (= ${binary:Version}),
|
||||
librust-proxmox-subscription-0.5.0+api-types-dev (= ${binary:Version})
|
||||
Description: Proxmox subscription utilitites - feature "api-types"
|
||||
This metapackage enables feature "api-types" for the Rust proxmox-subscription
|
||||
crate, by pulling in any additional dependencies needed by that feature.
|
||||
|
||||
Package: librust-proxmox-subscription+impl-dev
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
librust-proxmox-subscription-dev (= ${binary:Version}),
|
||||
librust-base64-0.13+default-dev,
|
||||
librust-hex-0.4+default-dev,
|
||||
librust-openssl-0.10+default-dev,
|
||||
librust-proxmox-http-0.9+client-trait-dev (>= 0.9.4-~~),
|
||||
librust-proxmox-http-0.9+default-dev (>= 0.9.4-~~),
|
||||
librust-proxmox-http-0.9+http-helpers-dev (>= 0.9.4-~~),
|
||||
librust-proxmox-sys-0.6+default-dev,
|
||||
librust-proxmox-time-2+default-dev
|
||||
Provides:
|
||||
librust-proxmox-subscription+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-subscription-0+impl-dev (= ${binary:Version}),
|
||||
librust-proxmox-subscription-0+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-subscription-0.5+impl-dev (= ${binary:Version}),
|
||||
librust-proxmox-subscription-0.5+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-subscription-0.5.0+impl-dev (= ${binary:Version}),
|
||||
librust-proxmox-subscription-0.5.0+default-dev (= ${binary:Version})
|
||||
Description: Proxmox subscription utilitites - feature "impl" and 1 more
|
||||
This metapackage enables feature "impl" for the Rust proxmox-subscription
|
||||
crate, by pulling in any additional dependencies needed by that feature.
|
||||
.
|
||||
Additionally, this package also provides the "default" feature.
|
||||
|
@ -1,10 +1,17 @@
|
||||
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
|
||||
|
||||
mod subscription_info;
|
||||
#[cfg(feature = "impl")]
|
||||
pub use subscription_info::{
|
||||
get_hardware_address, ProductType, SubscriptionInfo, SubscriptionStatus,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "impl"))]
|
||||
pub use subscription_info::{ProductType, SubscriptionInfo, SubscriptionStatus};
|
||||
|
||||
#[cfg(feature = "impl")]
|
||||
pub mod check;
|
||||
#[cfg(feature = "impl")]
|
||||
pub mod files;
|
||||
#[cfg(feature = "impl")]
|
||||
pub mod sign;
|
||||
|
@ -1,23 +1,11 @@
|
||||
use std::{fmt::Display, path::Path, str::FromStr};
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use openssl::hash::{hash, DigestBytes, MessageDigest};
|
||||
use proxmox_sys::fs::file_get_contents;
|
||||
use proxmox_time::TmEditor;
|
||||
use anyhow::{bail, Error};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "api-types")]
|
||||
use proxmox_schema::{api, Updater};
|
||||
|
||||
use crate::sign::Verifier;
|
||||
|
||||
pub(crate) const SHARED_KEY_DATA: &str = "kjfdlskfhiuewhfk947368";
|
||||
|
||||
/// How long the local key is valid for in between remote checks
|
||||
pub(crate) const SUBSCRIPTION_MAX_LOCAL_KEY_AGE: i64 = 15 * 24 * 3600;
|
||||
pub(crate) const SUBSCRIPTION_MAX_LOCAL_SIGNED_KEY_AGE: i64 = 365 * 24 * 3600;
|
||||
pub(crate) const SUBSCRIPTION_MAX_KEY_CHECK_FAILURE_AGE: i64 = 5 * 24 * 3600;
|
||||
|
||||
// Aliases are needed for PVE compat!
|
||||
#[cfg_attr(feature = "api-types", api())]
|
||||
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
@ -144,196 +132,226 @@ pub struct SubscriptionInfo {
|
||||
pub signature: Option<String>,
|
||||
}
|
||||
|
||||
impl SubscriptionInfo {
|
||||
/// Returns the canonicalized signed data and, if available, signature contained in `self`.
|
||||
pub fn signed_data(&self) -> Result<(Vec<u8>, Option<String>), Error> {
|
||||
let mut data = serde_json::to_value(self)?;
|
||||
let signature = data
|
||||
.as_object_mut()
|
||||
.ok_or_else(|| format_err!("subscription info not a JSON object"))?
|
||||
.remove("signature")
|
||||
.and_then(|v| v.as_str().map(|v| v.to_owned()));
|
||||
#[cfg(feature = "impl")]
|
||||
pub use _impl::get_hardware_address;
|
||||
|
||||
if self.is_signed() && signature.is_none() {
|
||||
bail!("Failed to extract signature value!");
|
||||
}
|
||||
#[cfg(feature = "impl")]
|
||||
pub(crate) use _impl::{md5sum, SHARED_KEY_DATA};
|
||||
|
||||
let data = proxmox_serde::json::to_canonical_json(&data)?;
|
||||
Ok((data, signature))
|
||||
}
|
||||
#[cfg(feature = "impl")]
|
||||
mod _impl {
|
||||
|
||||
/// Whether a signature exists - *this does not check the signature's validity!*
|
||||
///
|
||||
/// Use [SubscriptionInfo::check_signature()] to verify the
|
||||
/// signature.
|
||||
pub fn is_signed(&self) -> bool {
|
||||
self.signature.is_some()
|
||||
}
|
||||
use std::path::Path;
|
||||
|
||||
/// Checks whether a [SubscriptionInfo]'s `checktime` matches the age criteria:
|
||||
///
|
||||
/// - Instances generated (more than 1.5h) in the future are invalid
|
||||
/// - Signed instances are valid for up to a year, clamped by the next due date
|
||||
/// - Unsigned instances are valid for 30+5 days
|
||||
/// - If `recheck` is set to `true`, unsigned instances are only treated as valid for 5 days
|
||||
/// (this mode is used to decide whether to refresh the subscription information)
|
||||
///
|
||||
/// If the criteria are not met, `status` is set to [SubscriptionStatus::Invalid] and `message`
|
||||
/// to a human-readable error message.
|
||||
pub fn check_age(&mut self, recheck: bool) {
|
||||
let now = proxmox_time::epoch_i64();
|
||||
let age = now - self.checktime.unwrap_or(0);
|
||||
use anyhow::format_err;
|
||||
use anyhow::{bail, Error};
|
||||
use openssl::hash::{hash, DigestBytes, MessageDigest};
|
||||
use proxmox_sys::fs::file_get_contents;
|
||||
use proxmox_time::TmEditor;
|
||||
|
||||
let cutoff = if self.is_signed() {
|
||||
SUBSCRIPTION_MAX_LOCAL_SIGNED_KEY_AGE
|
||||
} else if recheck {
|
||||
SUBSCRIPTION_MAX_KEY_CHECK_FAILURE_AGE
|
||||
} else {
|
||||
SUBSCRIPTION_MAX_LOCAL_KEY_AGE + SUBSCRIPTION_MAX_KEY_CHECK_FAILURE_AGE
|
||||
};
|
||||
use crate::sign::Verifier;
|
||||
|
||||
// allow some delta for DST changes or time syncs, 1.5h
|
||||
if age < -5400 {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some("last check date too far in the future".to_string());
|
||||
self.signature = None;
|
||||
} else if age > cutoff {
|
||||
if let SubscriptionStatus::Active = self.status {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some("subscription information too old".to_string());
|
||||
self.signature = None;
|
||||
pub(crate) const SHARED_KEY_DATA: &str = "kjfdlskfhiuewhfk947368";
|
||||
|
||||
/// How long the local key is valid for in between remote checks
|
||||
pub(crate) const SUBSCRIPTION_MAX_LOCAL_KEY_AGE: i64 = 15 * 24 * 3600;
|
||||
pub(crate) const SUBSCRIPTION_MAX_LOCAL_SIGNED_KEY_AGE: i64 = 365 * 24 * 3600;
|
||||
pub(crate) const SUBSCRIPTION_MAX_KEY_CHECK_FAILURE_AGE: i64 = 5 * 24 * 3600;
|
||||
|
||||
use super::{ProductType, SubscriptionInfo, SubscriptionStatus};
|
||||
|
||||
impl SubscriptionInfo {
|
||||
/// Returns the canonicalized signed data and, if available, signature contained in `self`.
|
||||
pub fn signed_data(&self) -> Result<(Vec<u8>, Option<String>), Error> {
|
||||
let mut data = serde_json::to_value(self)?;
|
||||
let signature = data
|
||||
.as_object_mut()
|
||||
.ok_or_else(|| format_err!("subscription info not a JSON object"))?
|
||||
.remove("signature")
|
||||
.and_then(|v| v.as_str().map(|v| v.to_owned()));
|
||||
|
||||
if self.is_signed() && signature.is_none() {
|
||||
bail!("Failed to extract signature value!");
|
||||
}
|
||||
|
||||
let data = proxmox_serde::json::to_canonical_json(&data)?;
|
||||
Ok((data, signature))
|
||||
}
|
||||
|
||||
if self.is_signed() && self.status == SubscriptionStatus::Active {
|
||||
if let Some(next_due) = self.nextduedate.as_ref() {
|
||||
match parse_next_due(next_due.as_str()) {
|
||||
Ok(next_due) if now > next_due => {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some("subscription information too old".to_string());
|
||||
self.signature = None;
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some(format!("Failed parsing 'nextduedate' - {err}"));
|
||||
self.signature = None;
|
||||
/// Whether a signature exists - *this does not check the signature's validity!*
|
||||
///
|
||||
/// Use [SubscriptionInfo::check_signature()] to verify the
|
||||
/// signature.
|
||||
pub fn is_signed(&self) -> bool {
|
||||
self.signature.is_some()
|
||||
}
|
||||
|
||||
/// Checks whether a [SubscriptionInfo]'s `checktime` matches the age criteria:
|
||||
///
|
||||
/// - Instances generated (more than 1.5h) in the future are invalid
|
||||
/// - Signed instances are valid for up to a year, clamped by the next due date
|
||||
/// - Unsigned instances are valid for 30+5 days
|
||||
/// - If `recheck` is set to `true`, unsigned instances are only treated as valid for 5 days
|
||||
/// (this mode is used to decide whether to refresh the subscription information)
|
||||
///
|
||||
/// If the criteria are not met, `status` is set to [SubscriptionStatus::Invalid] and `message`
|
||||
/// to a human-readable error message.
|
||||
pub fn check_age(&mut self, recheck: bool) {
|
||||
let now = proxmox_time::epoch_i64();
|
||||
let age = now - self.checktime.unwrap_or(0);
|
||||
|
||||
let cutoff = if self.is_signed() {
|
||||
SUBSCRIPTION_MAX_LOCAL_SIGNED_KEY_AGE
|
||||
} else if recheck {
|
||||
SUBSCRIPTION_MAX_KEY_CHECK_FAILURE_AGE
|
||||
} else {
|
||||
SUBSCRIPTION_MAX_LOCAL_KEY_AGE + SUBSCRIPTION_MAX_KEY_CHECK_FAILURE_AGE
|
||||
};
|
||||
|
||||
// allow some delta for DST changes or time syncs, 1.5h
|
||||
if age < -5400 {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some("last check date too far in the future".to_string());
|
||||
self.signature = None;
|
||||
} else if age > cutoff {
|
||||
if let SubscriptionStatus::Active = self.status {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some("subscription information too old".to_string());
|
||||
self.signature = None;
|
||||
}
|
||||
}
|
||||
|
||||
if self.is_signed() && self.status == SubscriptionStatus::Active {
|
||||
if let Some(next_due) = self.nextduedate.as_ref() {
|
||||
match parse_next_due(next_due.as_str()) {
|
||||
Ok(next_due) if now > next_due => {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some("subscription information too old".to_string());
|
||||
self.signature = None;
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some(format!("Failed parsing 'nextduedate' - {err}"));
|
||||
self.signature = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that server ID contained in [SubscriptionInfo] matches that of current system.
|
||||
///
|
||||
/// `status` is set to [SubscriptionStatus::Invalid] and `message` to a human-readable
|
||||
/// message in case it does not.
|
||||
pub fn check_server_id(&mut self) {
|
||||
match (self.serverid.as_ref(), get_hardware_address()) {
|
||||
(_, Err(err)) => {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some(format!("Failed to obtain server ID - {err}."));
|
||||
self.signature = None;
|
||||
/// Check that server ID contained in [SubscriptionInfo] matches that of current system.
|
||||
///
|
||||
/// `status` is set to [SubscriptionStatus::Invalid] and `message` to a human-readable
|
||||
/// message in case it does not.
|
||||
pub fn check_server_id(&mut self) {
|
||||
match (self.serverid.as_ref(), get_hardware_address()) {
|
||||
(_, Err(err)) => {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some(format!("Failed to obtain server ID - {err}."));
|
||||
self.signature = None;
|
||||
}
|
||||
(None, _) => {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some("Missing server ID.".to_string());
|
||||
self.signature = None;
|
||||
}
|
||||
(Some(contained), Ok(expected)) if &expected != contained => {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some("Server ID mismatch.".to_string());
|
||||
self.signature = None;
|
||||
}
|
||||
(Some(_), Ok(_)) => {}
|
||||
}
|
||||
(None, _) => {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some("Missing server ID.".to_string());
|
||||
self.signature = None;
|
||||
}
|
||||
(Some(contained), Ok(expected)) if &expected != contained => {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some("Server ID mismatch.".to_string());
|
||||
self.signature = None;
|
||||
}
|
||||
(Some(_), Ok(_)) => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check a [SubscriptionInfo]'s signature, if one is available.
|
||||
///
|
||||
/// `status` is set to [SubscriptionStatus::Invalid] and `message` to a human-readable error
|
||||
/// message in case a signature is available but not valid for the given `key`.
|
||||
pub fn check_signature<P: AsRef<Path>>(&mut self, keys: &[P]) {
|
||||
let verify = |info: &SubscriptionInfo, path: &P| -> Result<(), Error> {
|
||||
let raw = file_get_contents(path)?;
|
||||
/// Check a [SubscriptionInfo]'s signature, if one is available.
|
||||
///
|
||||
/// `status` is set to [SubscriptionStatus::Invalid] and `message` to a human-readable error
|
||||
/// message in case a signature is available but not valid for the given `key`.
|
||||
pub fn check_signature<P: AsRef<Path>>(&mut self, keys: &[P]) {
|
||||
let verify = |info: &SubscriptionInfo, path: &P| -> Result<(), Error> {
|
||||
let raw = file_get_contents(path)?;
|
||||
|
||||
let key = openssl::pkey::PKey::public_key_from_pem(&raw)?;
|
||||
let key = openssl::pkey::PKey::public_key_from_pem(&raw)?;
|
||||
|
||||
let (signed, signature) = info.signed_data()?;
|
||||
let signature = match signature {
|
||||
None => bail!("Failed to extract signature value."),
|
||||
Some(sig) => sig,
|
||||
let (signed, signature) = info.signed_data()?;
|
||||
let signature = match signature {
|
||||
None => bail!("Failed to extract signature value."),
|
||||
Some(sig) => sig,
|
||||
};
|
||||
|
||||
key.verify(&signed, &signature)
|
||||
.map_err(|err| format_err!("Signature verification failed - {err}"))
|
||||
};
|
||||
|
||||
key.verify(&signed, &signature)
|
||||
.map_err(|err| format_err!("Signature verification failed - {err}"))
|
||||
};
|
||||
|
||||
if self.is_signed() {
|
||||
if keys.is_empty() {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some("Signature exists, but no key available.".to_string());
|
||||
} else if !keys.iter().any(|key| verify(self, key).is_ok()) {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some("Signature validation failed".to_string());
|
||||
if self.is_signed() {
|
||||
if keys.is_empty() {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some("Signature exists, but no key available.".to_string());
|
||||
} else if !keys.iter().any(|key| verify(self, key).is_ok()) {
|
||||
self.status = SubscriptionStatus::Invalid;
|
||||
self.message = Some("Signature validation failed".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_product_type(&self) -> Result<ProductType, Error> {
|
||||
self.key
|
||||
.as_ref()
|
||||
.ok_or_else(|| format_err!("no product key set"))
|
||||
.map(|key| key[..3].parse::<ProductType>())?
|
||||
}
|
||||
|
||||
pub fn get_next_due_date(&self) -> Result<i64, Error> {
|
||||
self.nextduedate
|
||||
.as_ref()
|
||||
.ok_or_else(|| format_err!("no next due date set"))
|
||||
.map(|e| parse_next_due(e))?
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_product_type(&self) -> Result<ProductType, Error> {
|
||||
self.key
|
||||
.as_ref()
|
||||
.ok_or_else(|| format_err!("no product key set"))
|
||||
.map(|key| key[..3].parse::<ProductType>())?
|
||||
/// Shortcut for md5 sums.
|
||||
pub(crate) fn md5sum(data: &[u8]) -> Result<DigestBytes, Error> {
|
||||
hash(MessageDigest::md5(), data).map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn get_next_due_date(&self) -> Result<i64, Error> {
|
||||
self.nextduedate
|
||||
.as_ref()
|
||||
.ok_or_else(|| format_err!("no next due date set"))
|
||||
.map(|e| parse_next_due(e))?
|
||||
}
|
||||
}
|
||||
/// Generate the current system's "server ID".
|
||||
pub fn get_hardware_address() -> Result<String, Error> {
|
||||
static FILENAME: &str = "/etc/ssh/ssh_host_rsa_key.pub";
|
||||
|
||||
/// Shortcut for md5 sums.
|
||||
pub(crate) fn md5sum(data: &[u8]) -> Result<DigestBytes, Error> {
|
||||
hash(MessageDigest::md5(), data).map_err(Error::from)
|
||||
}
|
||||
let contents = proxmox_sys::fs::file_get_contents(FILENAME)
|
||||
.map_err(|e| format_err!("Error getting host key - {}", e))?;
|
||||
let digest =
|
||||
md5sum(&contents).map_err(|e| format_err!("Error digesting host key - {}", e))?;
|
||||
|
||||
/// Generate the current system's "server ID".
|
||||
pub fn get_hardware_address() -> Result<String, Error> {
|
||||
static FILENAME: &str = "/etc/ssh/ssh_host_rsa_key.pub";
|
||||
|
||||
let contents = proxmox_sys::fs::file_get_contents(FILENAME)
|
||||
.map_err(|e| format_err!("Error getting host key - {}", e))?;
|
||||
let digest = md5sum(&contents).map_err(|e| format_err!("Error digesting host key - {}", e))?;
|
||||
|
||||
Ok(hex::encode(digest).to_uppercase())
|
||||
}
|
||||
|
||||
fn parse_next_due(value: &str) -> Result<i64, Error> {
|
||||
let mut components = value.split('-');
|
||||
let year = components
|
||||
.next()
|
||||
.ok_or_else(|| format_err!("missing year component."))?
|
||||
.parse::<i32>()?;
|
||||
let month = components
|
||||
.next()
|
||||
.ok_or_else(|| format_err!("missing month component."))?
|
||||
.parse::<i32>()?;
|
||||
let day = components
|
||||
.next()
|
||||
.ok_or_else(|| format_err!("missing day component."))?
|
||||
.parse::<i32>()?;
|
||||
|
||||
if components.next().is_some() {
|
||||
bail!("cannot parse 'nextduedate' value '{value}'");
|
||||
Ok(hex::encode(digest).to_uppercase())
|
||||
}
|
||||
|
||||
let mut tm = TmEditor::new(true);
|
||||
tm.set_year(year)?;
|
||||
tm.set_mon(month)?;
|
||||
tm.set_mday(day)?;
|
||||
fn parse_next_due(value: &str) -> Result<i64, Error> {
|
||||
let mut components = value.split('-');
|
||||
let year = components
|
||||
.next()
|
||||
.ok_or_else(|| format_err!("missing year component."))?
|
||||
.parse::<i32>()?;
|
||||
let month = components
|
||||
.next()
|
||||
.ok_or_else(|| format_err!("missing month component."))?
|
||||
.parse::<i32>()?;
|
||||
let day = components
|
||||
.next()
|
||||
.ok_or_else(|| format_err!("missing day component."))?
|
||||
.parse::<i32>()?;
|
||||
|
||||
tm.into_epoch()
|
||||
if components.next().is_some() {
|
||||
bail!("cannot parse 'nextduedate' value '{value}'");
|
||||
}
|
||||
|
||||
let mut tm = TmEditor::new(true);
|
||||
tm.set_year(year)?;
|
||||
tm.set_mon(month)?;
|
||||
tm.set_mday(day)?;
|
||||
|
||||
tm.into_epoch()
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-sys
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -19,9 +19,10 @@ Build-Depends: debhelper (>= 12),
|
||||
libacl1-dev <!nocheck>,
|
||||
uuid-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-sys
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -24,7 +24,7 @@ pub fn change_cloexec(fd: RawFd, on: bool) -> Result<(), anyhow::Error> {
|
||||
}
|
||||
|
||||
pub(crate) fn cwd() -> Result<OwnedFd, nix::Error> {
|
||||
open(".", OFlag::O_DIRECTORY, stat::Mode::empty())
|
||||
open(".", crate::fs::DIR_FLAGS, stat::Mode::empty())
|
||||
}
|
||||
|
||||
pub fn open<P>(path: &P, oflag: OFlag, mode: Mode) -> Result<OwnedFd, nix::Error>
|
||||
|
@ -14,6 +14,11 @@ use proxmox_lang::try_block;
|
||||
|
||||
use crate::fs::{fchown, CreateOptions};
|
||||
|
||||
/// The default list of [`OFlag`]'s we want to use when opening directories. Besides ensuring that
|
||||
/// the FD indeed points to a directory we also must ensure that it gets closed on exec to avoid
|
||||
/// leaking a open FD to a child process.
|
||||
pub(crate) const DIR_FLAGS: OFlag = OFlag::O_DIRECTORY.union(OFlag::O_CLOEXEC);
|
||||
|
||||
/// Creates directory at the provided path with specified ownership.
|
||||
///
|
||||
/// Errors if the directory already exists.
|
||||
@ -66,7 +71,7 @@ pub fn ensure_dir_exists<P: AsRef<Path>>(
|
||||
Err(err) => bail!("unable to create directory {path:?} - {err}",),
|
||||
}
|
||||
|
||||
let fd = nix::fcntl::open(path, OFlag::O_DIRECTORY, stat::Mode::empty())
|
||||
let fd = nix::fcntl::open(path, DIR_FLAGS, stat::Mode::empty())
|
||||
.map(|fd| unsafe { OwnedFd::from_raw_fd(fd) })
|
||||
.map_err(|err| format_err!("unable to open created directory {path:?} - {err}"))?;
|
||||
// umask defaults to 022 so make sure the mode is fully honowed:
|
||||
@ -120,7 +125,7 @@ fn create_path_do(
|
||||
Some(Component::Prefix(_)) => bail!("illegal prefix path component encountered"),
|
||||
Some(Component::RootDir) => {
|
||||
let _ = iter.next();
|
||||
crate::fd::open(c"/", OFlag::O_DIRECTORY, stat::Mode::empty())?
|
||||
crate::fd::open(c"/", DIR_FLAGS, stat::Mode::empty())?
|
||||
}
|
||||
Some(Component::CurDir) => {
|
||||
let _ = iter.next();
|
||||
@ -128,7 +133,7 @@ fn create_path_do(
|
||||
}
|
||||
Some(Component::ParentDir) => {
|
||||
let _ = iter.next();
|
||||
crate::fd::open(c"..", OFlag::O_DIRECTORY, stat::Mode::empty())?
|
||||
crate::fd::open(c"..", DIR_FLAGS, stat::Mode::empty())?
|
||||
}
|
||||
Some(Component::Normal(_)) => {
|
||||
// simply do not advance the iterator, heavy lifting happens in create_path_at_do()
|
||||
@ -154,7 +159,7 @@ fn create_path_at_do(
|
||||
None => return Ok(created),
|
||||
|
||||
Some(Component::ParentDir) => {
|
||||
at = crate::fd::openat(&at, c"..", OFlag::O_DIRECTORY, stat::Mode::empty())?;
|
||||
at = crate::fd::openat(&at, c"..", DIR_FLAGS, stat::Mode::empty())?;
|
||||
}
|
||||
|
||||
Some(Component::Normal(path)) => {
|
||||
@ -175,7 +180,7 @@ fn create_path_at_do(
|
||||
Err(e) => return Err(e.into()),
|
||||
Ok(_) => true,
|
||||
};
|
||||
at = crate::fd::openat(&at, path, OFlag::O_DIRECTORY, stat::Mode::empty())?;
|
||||
at = crate::fd::openat(&at, path, DIR_FLAGS, stat::Mode::empty())?;
|
||||
|
||||
if let (true, Some(opts)) = (created, opts) {
|
||||
if opts.owner.is_some() || opts.group.is_some() {
|
||||
@ -222,7 +227,7 @@ pub fn make_tmp_dir<P: AsRef<Path>>(
|
||||
|
||||
if let Some(options) = options {
|
||||
if let Err(err) = try_block!({
|
||||
let mut fd = crate::fd::open(&path, OFlag::O_DIRECTORY, stat::Mode::empty())?;
|
||||
let mut fd = crate::fd::open(&path, DIR_FLAGS, stat::Mode::empty())?;
|
||||
options.apply_to(&mut fd, &path)?;
|
||||
Ok::<(), Error>(())
|
||||
}) {
|
||||
|
@ -116,6 +116,29 @@ pub fn file_read_firstline<P: AsRef<Path>>(path: P) -> Result<String, Error> {
|
||||
read_firstline(path).map_err(|err| format_err!("unable to read {path:?} - {err}"))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Creates a tmpfile like [`nix::unistd::mkstemp`], but with [`nix::fctnl::Oflag`] set.
|
||||
///
|
||||
/// Note that some flags are masked out since they can produce an error, see mkostemp(2) for details.
|
||||
// code is mostly copied from nix mkstemp
|
||||
fn mkostemp<P: ?Sized + NixPath>(
|
||||
template: &P,
|
||||
oflag: OFlag,
|
||||
) -> nix::Result<(std::os::fd::RawFd, PathBuf)> {
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
let mut path = template.with_nix_path(|path| path.to_bytes_with_nul().to_owned())?;
|
||||
let p = path.as_mut_ptr().cast();
|
||||
|
||||
let flags = OFlag::intersection(OFlag::O_APPEND | OFlag::O_CLOEXEC | OFlag::O_SYNC, oflag);
|
||||
|
||||
let fd = unsafe { libc::mkostemp(p, flags.bits()) };
|
||||
let last = path.pop(); // drop the trailing nul
|
||||
debug_assert!(last == Some(b'\0'));
|
||||
let pathname = std::ffi::OsString::from_vec(path);
|
||||
Errno::result(fd)?;
|
||||
Ok((fd, PathBuf::from(pathname)))
|
||||
}
|
||||
|
||||
/// Takes a Path and CreateOptions, creates a tmpfile from it and returns
|
||||
/// a RawFd and PathBuf for it
|
||||
pub fn make_tmp_file<P: AsRef<Path>>(
|
||||
@ -127,7 +150,7 @@ pub fn make_tmp_file<P: AsRef<Path>>(
|
||||
// use mkstemp here, because it works with different processes, threads, even tokio tasks
|
||||
let mut template = path.to_owned();
|
||||
template.set_extension("tmp_XXXXXX");
|
||||
let (mut file, tmp_path) = match unistd::mkstemp(&template) {
|
||||
let (mut file, tmp_path) = match mkostemp(&template, OFlag::O_CLOEXEC) {
|
||||
Ok((fd, path)) => (unsafe { File::from_raw_fd(fd) }, path),
|
||||
Err(err) => bail!("mkstemp {:?} failed: {}", template, err),
|
||||
};
|
||||
|
@ -1,10 +1,10 @@
|
||||
Source: rust-proxmox-syslog-api
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
rustc:native (>= 1.80) <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-anyhow-1+default-dev <!nocheck>,
|
||||
librust-proxmox-schema-3+api-macro-dev (>= 3.1.1-~~) <!nocheck>,
|
||||
@ -13,9 +13,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+default-dev <!nocheck>,
|
||||
librust-serde-1+derive-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-syslog-api
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,17 +1,18 @@
|
||||
Source: rust-proxmox-systemd
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-libc-0.2+default-dev (>= 0.2.107-~~) <!nocheck>,
|
||||
libsystemd-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-systemd
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-tfa
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -13,7 +13,7 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+default-dev <!nocheck>,
|
||||
librust-serde-plain-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
|
@ -1,8 +1,8 @@
|
||||
Source: rust-proxmox-time-api
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
@ -13,9 +13,10 @@ Build-Depends: debhelper (>= 12),
|
||||
librust-serde-1+default-dev <!nocheck>,
|
||||
librust-serde-1+derive-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-time-api
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "proxmox-time"
|
||||
description = "time utilities and TmEditor"
|
||||
version = "2.0.2"
|
||||
version = "2.0.3"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
@ -1,3 +1,10 @@
|
||||
rust-proxmox-time (2.0.3-1) bookworm; urgency=medium
|
||||
|
||||
* also implement `From<&TimeSpan> for f64` to support taking a reference, as
|
||||
we do not require ownership here.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 26 Nov 2024 16:52:28 +0100
|
||||
|
||||
rust-proxmox-time (2.0.2-1) bookworm; urgency=medium
|
||||
|
||||
* time span:
|
||||
|
@ -37,7 +37,7 @@ Provides:
|
||||
librust-proxmox-time-2+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-time-2.0-dev (= ${binary:Version}),
|
||||
librust-proxmox-time-2.0+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-time-2.0.2-dev (= ${binary:Version}),
|
||||
librust-proxmox-time-2.0.2+default-dev (= ${binary:Version})
|
||||
librust-proxmox-time-2.0.3-dev (= ${binary:Version}),
|
||||
librust-proxmox-time-2.0.3+default-dev (= ${binary:Version})
|
||||
Description: Time utilities and TmEditor - Rust source code
|
||||
Source code for Debianized Rust crate "proxmox-time"
|
||||
|
@ -1,14 +1,14 @@
|
||||
//! Timespans that try to be compatible with the systemd time span format.
|
||||
//!
|
||||
//! Time spans refer to time durations, like [std::time::Duration] but in the format that is
|
||||
//! targetting human interfaces and that systemd understands. Parts of this documentation have been
|
||||
//! targeting human interfaces and that systemd understands. Parts of this documentation have been
|
||||
//! adapted from the systemd.time manual page.
|
||||
//!
|
||||
//! The following time units are understood:
|
||||
//! - `nsec`, `ns` (not always accepted by systemd.time)
|
||||
//! - `usec`, `us`, `µs`
|
||||
//! - `msec`, `ms`
|
||||
//! - `seconds`, s`econd`, `sec`, `s`
|
||||
//! - `seconds`, `second`, `sec`, `s`
|
||||
//! - `minutes`, `minute`, `min`, `m`
|
||||
//! - `hours`, `hour`, `hr`, `h`
|
||||
//! - `days`, `day`, `d`
|
||||
@ -26,7 +26,7 @@
|
||||
//! spaces between units and/or values can be added or omitted. The order of the time values does
|
||||
//! not matter.
|
||||
//!
|
||||
//! The following examples are all represeting the exact same time span of 1 day 2 hours and 3
|
||||
//! The following examples are all representing the exact same time span of 1 day 2 hours and 3
|
||||
//! minutes:
|
||||
//!
|
||||
//! - `1d 2h 3m`
|
||||
@ -128,8 +128,8 @@ pub struct TimeSpan {
|
||||
pub years: u64,
|
||||
}
|
||||
|
||||
impl From<TimeSpan> for f64 {
|
||||
fn from(ts: TimeSpan) -> Self {
|
||||
impl From<&TimeSpan> for f64 {
|
||||
fn from(ts: &TimeSpan) -> Self {
|
||||
(ts.seconds as f64)
|
||||
+ ((ts.nsec as f64) / 1_000_000_000.0)
|
||||
+ ((ts.usec as f64) / 1_000_000.0)
|
||||
@ -143,6 +143,12 @@ impl From<TimeSpan> for f64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TimeSpan> for f64 {
|
||||
fn from(ts: TimeSpan) -> Self {
|
||||
Self::from(&ts)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::time::Duration> for TimeSpan {
|
||||
fn from(duration: std::time::Duration) -> Self {
|
||||
let mut duration = duration.as_nanos();
|
||||
@ -306,8 +312,8 @@ pub fn verify_time_span(i: &str) -> Result<(), Error> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn conversions() {
|
||||
|
@ -1,17 +1,18 @@
|
||||
Source: rust-proxmox-uuid
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-js-sys-0.3+default-dev (>= 0.3.55-~~) <!nocheck>,
|
||||
uuid-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-uuid
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
@ -1,16 +1,17 @@
|
||||
Source: rust-proxmox-worker-task
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-sequence-cargo,
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-anyhow-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.0
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
Homepage: https://proxmox.com
|
||||
X-Cargo-Crate: proxmox-worker-task
|
||||
Rules-Requires-Root: no
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user