1 Commits

Author SHA1 Message Date
b900c87a8f buildsys: drop bullseye from *stable* branch upload target
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-20 09:49:17 +02:00
406 changed files with 6325 additions and 1180514 deletions

4
.gitignore vendored
View File

@ -2,7 +2,3 @@
/*/target /*/target
Cargo.lock Cargo.lock
**/*.rs.bk **/*.rs.bk
/*.buildinfo
/*.changes
/build
/*-deb

View File

@ -1,100 +1,10 @@
[workspace] [workspace]
members = [ members = [
"proxmox",
"proxmox-api-macro", "proxmox-api-macro",
"proxmox-apt",
"proxmox-async",
"proxmox-auth-api",
"proxmox-borrow",
"proxmox-compression",
"proxmox-http", "proxmox-http",
"proxmox-io",
"proxmox-lang",
"proxmox-ldap",
"proxmox-login",
"proxmox-metrics",
"proxmox-openid",
"proxmox-rest-server",
"proxmox-router",
"proxmox-schema",
"proxmox-section-config",
"proxmox-serde",
"proxmox-shared-memory",
"proxmox-sortable-macro", "proxmox-sortable-macro",
"proxmox-subscription",
"proxmox-sys",
"proxmox-tfa",
"proxmox-time",
"proxmox-uuid",
] ]
exclude = [ exclude = [
"build", "build",
] ]
[workspace.package]
authors = ["Proxmox Support Team <support@proxmox.com>"]
edition = "2021"
license = "AGPL-3"
repository = "https://git.proxmox.com/?p=proxmox.git"
exclude = [ "debian" ]
homepage = "https://www.proxmox.com"
[workspace.dependencies]
# any features enabled here are enabled on all members using 'workspace = true'!
# external dependencies
anyhow = "1.0"
base32 = "0.4"
base64 = "0.13"
bytes = "1.0"
crc32fast = "1"
endian_trait = "0.6"
flate2 = "1.0"
futures = "0.3"
handlebars = "3.0"
hex = "0.4"
http = "0.2"
hyper = "0.14.5"
lazy_static = "1.4"
ldap3 = { version = "0.11", default-features = false }
libc = "0.2.107"
log = "0.4.17"
native-tls = "0.2"
nix = "0.26.1"
once_cell = "1.3.1"
openssl = "0.10"
pam = "0.7"
pam-sys = "0.5"
percent-encoding = "2.1"
pin-utils = "0.1.0"
proc-macro2 = "1.0"
quote = "1.0"
regex = "1.5"
serde = "1.0"
serde_json = "1.0"
serde_plain = "1.0"
syn = { version = "1.0", features = [ "full", "visit-mut" ] }
tar = "0.4"
tokio = "1.6"
tokio-openssl = "0.6.1"
tokio-stream = "0.1.0"
tower-service = "0.3.0"
url = "2.2"
walkdir = "2"
webauthn-rs = "0.3"
zstd = { version = "0.6", features = [ "bindgen" ] }
# workspace dependencies
proxmox-api-macro = { version = "1.0.4", path = "proxmox-api-macro" }
proxmox-async = { version = "0.4.1", path = "proxmox-async" }
proxmox-compression = { version = "0.1.1", path = "proxmox-compression" }
proxmox-http = { version = "0.8.0", path = "proxmox-http" }
proxmox-io = { version = "1.0.0", path = "proxmox-io" }
proxmox-lang = { version = "1.1", path = "proxmox-lang" }
proxmox-rest-server = { version = "0.3.0", path = "proxmox-rest-server" }
proxmox-router = { version = "1.3.1", path = "proxmox-router" }
proxmox-schema = { version = "1.3.7", path = "proxmox-schema" }
proxmox-serde = { version = "0.1.1", path = "proxmox-serde", features = [ "serde_json" ] }
proxmox-sortable-macro = { version = "0.1.2", path = "proxmox-sortable-macro" }
proxmox-sys = { version = "0.4.2", path = "proxmox-sys" }
proxmox-tfa = { version = "4.0.0", path = "proxmox-tfa" }
proxmox-time = { version = "1.1.4", path = "proxmox-time" }
proxmox-uuid = { version = "1.0.1", path = "proxmox-uuid" }

View File

@ -1,6 +1,6 @@
# Shortcut for common operations: # Shortcut for common operations:
CRATES != cargo metadata --format-version=1 | jq -r .workspace_members'[]' | awk '{ print $$1 }' CRATES=proxmox proxmox-api-macro proxmox-http proxmox-sortable-macro
# By default we just run checks: # By default we just run checks:
.PHONY: all .PHONY: all
@ -11,11 +11,6 @@ deb: $(foreach c,$(CRATES), $c-deb)
echo $(foreach c,$(CRATES), $c-deb) echo $(foreach c,$(CRATES), $c-deb)
lintian build/*.deb lintian build/*.deb
.PHONY: dsc
dsc: $(foreach c,$(CRATES), $c-dsc)
echo $(foreach c,$(CRATES), $c-dsc)
lintian build/*.dsc
.PHONY: autopkgtest .PHONY: autopkgtest
autopkgtest: $(foreach c,$(CRATES), $c-autopkgtest) autopkgtest: $(foreach c,$(CRATES), $c-autopkgtest)
@ -29,10 +24,6 @@ dinstall:
./build.sh $* ./build.sh $*
touch $@ touch $@
%-dsc:
BUILDCMD='dpkg-buildpackage -S -us -uc -d' ./build.sh $*
touch $@
%-autopkgtest: %-autopkgtest:
autopkgtest build/$* build/*.deb -- null autopkgtest build/$* build/*.deb -- null
touch $@ touch $@
@ -60,8 +51,7 @@ doc:
.PHONY: clean .PHONY: clean
clean: clean:
cargo clean cargo clean
rm -rf build/ rm -rf build *-deb *-autopkgtest
rm -f -- *-deb *-dsc *-autopkgtest *.buildinfo *.changes
.PHONY: update .PHONY: update
update: update:
@ -72,4 +62,4 @@ update:
dcmd --deb rust-$*_*.changes \ dcmd --deb rust-$*_*.changes \
| grep -v '.changes$$' \ | grep -v '.changes$$' \
| tar -cf "$@.tar" -T-; \ | tar -cf "$@.tar" -T-; \
cat "$@.tar" | ssh -X repoman@repo.proxmox.com upload --product devel --dist bullseye cat "$@.tar" | ssh -X repoman@repo.proxmox.com upload --product devel --dist buster

View File

@ -14,66 +14,9 @@ the dependency needs to point directly to a path or git source.
Steps for Releases Steps for Releases
================== ==================
- Run ./bump.sh <CRATE> [patch|minor|major|<VERSION>] - Cargo.toml updates:
-- Fill out changelog - Bump all modified crate versions.
-- Confirm bump commit - Update all the other crates' Cargo.toml to depend on the new versions if
required, then bump their version as well if not already done.
- Update debian/changelog files in all the crates updated above.
- Build packages with `make deb`. - Build packages with `make deb`.
-- Don't forget to commit updated d/control!
Adding Crates
=============
1) At the top level:
- Generate the crate: ``cargo new --lib the-name``
- Sort the crate into ``Cargo.toml``'s ``workspace.members``
2) In the new crate's ``Cargo.toml``:
- In ``[package]`` set:
authors.workspace = true
license.workspace = true
edition.workspace = true
exclude.workspace = true
- Add a meaningful ``description``
- Copy ``debian/copyright`` and ``debian/debcargo.toml`` from another subcrate.
Adding a new Dependency
=======================
1) At the top level:
- Add it to ``[workspace.dependencies]`` specifying the version and any
features that should be enabled throughout the workspace
2) In each member's ``Cargo.toml``:
- Add it to the desired dependencies section with ``workspace = true`` and no
version specified.
- If this member requires additional features, add only the extra features to
the member dependency.
Updating a Dependency's Version
===============================
1) At the top level:
- Bump the version in ``[workspace.dependencies]`` as desired.
- Check for deprecations or breakage throughout the workspace.
Notes on Workspace Inheritance
==============================
Common metadata (like authors, license, ..) are inherited throughout the
workspace. If new fields are added that are identical for all crates, they
should be defined in the top-level ``Cargo.toml`` file's
``[workspace.package]`` section, and inherited in all members explicitly by
setting ``FIELD.workspace = true`` in the member's ``[package]`` section.
Dependency information is also inherited throughout the workspace, allowing a
single dependency specification in the top-level Cargo.toml file to be used by
all members.
Some restrictions apply:
- features can only be added in members, never removed (this includes
``default_features = false``!)
- the base feature set at the workspace level should be the minimum (possibly
empty!) set required by all members
- workspace dependency specifications cannot include ``optional``
- if needed, the ``optional`` flag needs to be set at the member level when
using a workspace dependency

44
bump.sh
View File

@ -1,44 +0,0 @@
#!/bin/bash
package=$1
if [[ -z "$package" ]]; then
echo "USAGE:"
echo -e "\t bump.sh <crate> [patch|minor|major|<version>]"
echo ""
echo "Defaults to bumping patch version by 1"
exit 0
fi
cargo_set_version="$(command -v cargo-set-version)"
if [[ -z "$cargo_set_version" || ! -x "$cargo_set_version" ]]; then
echo 'bump.sh requires "cargo set-version", provided by "cargo-edit".'
exit 1
fi
if [[ ! -e "$package/Cargo.toml" ]]; then
echo "Invalid crate '$package'"
exit 1
fi
version=$2
if [[ -z "$version" ]]; then
version="patch"
fi
case "$version" in
patch|minor|major)
bump="--bump"
;;
*)
bump=
;;
esac
cargo_toml="$package/Cargo.toml"
changelog="$package/debian/changelog"
cargo set-version -p "$package" $bump "$version"
version="$(cargo metadata --format-version=1 | jq ".packages[] | select(.name == \"$package\").version" | sed -e 's/\"//g')"
DEBFULLNAME="Proxmox Support Team" DEBEMAIL="support@proxmox.com" dch --no-conf --changelog "$changelog" --newversion "$version-1" --distribution stable
git commit --edit -sm "bump $package to $version-1" Cargo.toml "$cargo_toml" "$changelog"

View File

@ -1,35 +1,28 @@
[package] [package]
name = "proxmox-api-macro" name = "proxmox-api-macro"
edition.workspace = true edition = "2018"
version = "1.0.4" version = "0.3.4"
authors.workspace = true authors = [ "Wolfgang Bumiller <w.bumiller@proxmox.com>" ]
license.workspace = true license = "AGPL-3"
repository.workspace = true
description = "Proxmox API macro" description = "Proxmox API macro"
exclude.workspace = true exclude = [ "debian" ]
[lib] [lib]
proc-macro = true proc-macro = true
[dependencies] [dependencies]
anyhow.workspace = true anyhow = "1.0"
proc-macro2.workspace = true proc-macro2 = "1.0"
quote.workspace = true quote = "1.0"
syn = { workspace = true , features = [ "extra-traits" ] } syn = { version = "1.0", features = [ "extra-traits", "full", "visit-mut" ] }
[dev-dependencies] [dev-dependencies]
futures.workspace = true futures = "0.3"
serde = { workspace = true, features = [ "derive" ] } proxmox = { version = "0.11.0", path = "../proxmox", features = [ "test-harness", "api-macro" ] }
serde_json.workspace = true serde = "1.0"
serde_derive = "1.0"
[dev-dependencies.proxmox-schema] serde_json = "1.0"
workspace = true
features = [ "test-harness", "api-macro" ]
[dev-dependencies.proxmox-router]
workspace = true
features = [ "test-harness" ]
# [features] # [features]
# # Used to quickly filter out the serde derive noise when using `cargo expand` for debugging! # # Used to quickly filter out the serde derive noise when using `cargo expand` for debugging!

View File

@ -1,61 +1,3 @@
rust-proxmox-api-macro (1.0.4-1) stable; urgency=medium
* support #[default] attribute for types which derive Default
* documentation updates
-- Proxmox Support Team <support@proxmox.com> Mon, 12 Dec 2022 11:31:34 +0100
rust-proxmox-api-macro (1.0.3-1) stable; urgency=medium
* allow overriding fiel attributes when deriving an updater
-- Proxmox Support Team <support@proxmox.com> Thu, 19 May 2022 12:03:36 +0200
rust-proxmox-api-macro (1.0.2-1) stable; urgency=medium
* support streaming api handlers
-- Proxmox Support Team <support@proxmox.com> Tue, 12 Apr 2022 14:26:46 +0200
rust-proxmox-api-macro (1.0.1-1) stable; urgency=medium
* stop adding automatically_derived to derived output to please new rustc
-- Proxmox Support Team <support@proxmox.com> Tue, 12 Oct 2021 14:49:35 +0200
rust-proxmox-api-macro (1.0.0-1) stable; urgency=medium
* schema was split out of proxmox into a new proxmox-schema crate
-- Proxmox Support Team <support@proxmox.com> Thu, 07 Oct 2021 14:28:14 +0200
rust-proxmox-api-macro (0.5.1-1) stable; urgency=medium
* allow external `returns` specification on methods, refereincing a
`ReturnType`.
-- Proxmox Support Team <support@proxmox.com> Mon, 30 Aug 2021 10:44:21 +0200
rust-proxmox-api-macro (0.5.0-1) stable; urgency=medium
* for non structs without Updater types and methods, `type: Foo` can now be
omitted for api types
* Adapt to the changes to Updatable in the proxmox crate
* Updaters have no try_build_from or update_from method anymore for now
* #[api] types automatically implement the new ApiType trait
-- Proxmox Support Team <support@proxmox.com> Tue, 24 Aug 2021 15:22:05 +0200
rust-proxmox-api-macro (0.4.0-1) stable; urgency=medium
* update proxmox to 0.12.0
-- Proxmox Support Team <support@proxmox.com> Tue, 20 Jul 2021 17:09:40 +0200
rust-proxmox-api-macro (0.3.4-1) unstable; urgency=medium rust-proxmox-api-macro (0.3.4-1) unstable; urgency=medium
* fix path in generated Updatable derive entry to not require explicit * fix path in generated Updatable derive entry to not require explicit

View File

@ -1,8 +1,8 @@
Source: rust-proxmox-api-macro Source: rust-proxmox-api-macro
Section: rust Section: rust
Priority: optional Priority: optional
Build-Depends: debhelper (>= 12), Build-Depends: debhelper (>= 11),
dh-cargo (>= 25), dh-cargo (>= 18),
cargo:native <!nocheck>, cargo:native <!nocheck>,
rustc:native <!nocheck>, rustc:native <!nocheck>,
libstd-rust-dev <!nocheck>, libstd-rust-dev <!nocheck>,
@ -14,11 +14,9 @@ Build-Depends: debhelper (>= 12),
librust-syn-1+full-dev <!nocheck>, librust-syn-1+full-dev <!nocheck>,
librust-syn-1+visit-mut-dev <!nocheck> librust-syn-1+visit-mut-dev <!nocheck>
Maintainer: Proxmox Support Team <support@proxmox.com> Maintainer: Proxmox Support Team <support@proxmox.com>
Standards-Version: 4.6.1 Standards-Version: 4.4.1
Vcs-Git: git://git.proxmox.com/git/proxmox.git Vcs-Git: git://git.proxmox.com/git/proxmox.git
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
X-Cargo-Crate: proxmox-api-macro
Rules-Requires-Root: no
Package: librust-proxmox-api-macro-dev Package: librust-proxmox-api-macro-dev
Architecture: any Architecture: any
@ -34,12 +32,12 @@ Depends:
librust-syn-1+visit-mut-dev librust-syn-1+visit-mut-dev
Provides: Provides:
librust-proxmox-api-macro+default-dev (= ${binary:Version}), librust-proxmox-api-macro+default-dev (= ${binary:Version}),
librust-proxmox-api-macro-1-dev (= ${binary:Version}), librust-proxmox-api-macro-0-dev (= ${binary:Version}),
librust-proxmox-api-macro-1+default-dev (= ${binary:Version}), librust-proxmox-api-macro-0+default-dev (= ${binary:Version}),
librust-proxmox-api-macro-1.0-dev (= ${binary:Version}), librust-proxmox-api-macro-0.3-dev (= ${binary:Version}),
librust-proxmox-api-macro-1.0+default-dev (= ${binary:Version}), librust-proxmox-api-macro-0.3+default-dev (= ${binary:Version}),
librust-proxmox-api-macro-1.0.4-dev (= ${binary:Version}), librust-proxmox-api-macro-0.3.4-dev (= ${binary:Version}),
librust-proxmox-api-macro-1.0.4+default-dev (= ${binary:Version}) librust-proxmox-api-macro-0.3.4+default-dev (= ${binary:Version})
Description: Proxmox API macro - Rust source code Description: Proxmox API macro - Rust source code
This package contains the source for the Rust proxmox-api-macro crate, packaged This package contains the source for the Rust proxmox-api-macro crate, packaged
by debcargo for use with cargo and dh-cargo. by debcargo for use with cargo and dh-cargo.

View File

@ -1,18 +1,16 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Copyright (C) 2019 Proxmox Server Solutions GmbH
Files: This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
*
Copyright: 2019 - 2023 Proxmox Server Solutions GmbH <support@proxmox.com> This program is free software: you can redistribute it and/or modify
License: AGPL-3.0-or-later it under the terms of the GNU Affero General Public License as published by
This program is free software: you can redistribute it and/or modify it under the Free Software Foundation, either version 3 of the License, or
the terms of the GNU Affero General Public License as published by the Free (at your option) any later version.
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
This program is distributed in the hope that it will be useful, but WITHOUT MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS GNU Affero General Public License for more details.
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 <http://www.gnu.org/licenses/>.
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/>.

View File

@ -1,75 +0,0 @@
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{Meta, NestedMeta};
use crate::util::{self, default_false, parse_str_value_to_option, set_bool};
#[derive(Default)]
pub struct UpdaterFieldAttributes {
/// Skip this field in the updater.
skip: Option<syn::LitBool>,
/// Change the type for the updater.
ty: Option<syn::TypePath>,
/// Replace any `#[serde]` attributes on the field with these (accumulates).
serde: Vec<syn::Attribute>,
}
impl UpdaterFieldAttributes {
pub fn from_attributes(input: &mut Vec<syn::Attribute>) -> Self {
let mut this = Self::default();
util::extract_attributes(input, "updater", |attr, meta| this.parse(attr, meta));
this
}
fn parse(&mut self, attr: &syn::Attribute, input: NestedMeta) -> Result<(), syn::Error> {
match input {
NestedMeta::Lit(lit) => bail!(lit => "unexpected literal"),
NestedMeta::Meta(meta) => self.parse_meta(attr, meta),
}
}
fn parse_meta(&mut self, attr: &syn::Attribute, meta: Meta) -> Result<(), syn::Error> {
match meta {
Meta::Path(ref path) if path.is_ident("skip") => {
set_bool(&mut self.skip, path, true);
}
Meta::NameValue(ref nv) if nv.path.is_ident("type") => {
parse_str_value_to_option(&mut self.ty, nv)
}
Meta::NameValue(m) => bail!(&m => "invalid updater attribute: {:?}", m.path),
Meta::List(m) if m.path.is_ident("serde") => {
let mut tokens = TokenStream::new();
m.paren_token
.surround(&mut tokens, |tokens| m.nested.to_tokens(tokens));
self.serde.push(syn::Attribute {
path: m.path,
tokens,
..attr.clone()
});
}
Meta::List(m) => bail!(&m => "invalid updater attribute: {:?}", m.path),
Meta::Path(m) => bail!(&m => "invalid updater attribute: {:?}", m),
}
Ok(())
}
pub fn skip(&self) -> bool {
default_false(self.skip.as_ref())
}
pub fn ty(&self) -> Option<&syn::TypePath> {
self.ty.as_ref()
}
pub fn replace_serde_attributes(&self, attrs: &mut Vec<syn::Attribute>) {
if !self.serde.is_empty() {
attrs.retain(|attr| !attr.path.is_ident("serde"));
attrs.extend(self.serde.iter().cloned())
}
}
}

View File

@ -25,8 +25,6 @@ pub fn handle_enum(
error!(fmt.span(), "illegal key 'format', will be autogenerated"); error!(fmt.span(), "illegal key 'format', will be autogenerated");
} }
let has_default_attrib = attribs.get("default").map(|def| def.span());
let schema = { let schema = {
let mut schema: Schema = attribs.try_into()?; let mut schema: Schema = attribs.try_into()?;
@ -41,8 +39,6 @@ pub fn handle_enum(
}; };
let container_attrs = serde::ContainerAttrib::try_from(&enum_ty.attrs[..])?; let container_attrs = serde::ContainerAttrib::try_from(&enum_ty.attrs[..])?;
let derives_default = util::derives_trait(&enum_ty.attrs, "Default");
let mut default_value = None;
let mut variants = TokenStream::new(); let mut variants = TokenStream::new();
for variant in &mut enum_ty.variants { for variant in &mut enum_ty.variants {
@ -59,7 +55,7 @@ pub fn handle_enum(
let attrs = serde::SerdeAttrib::try_from(&variant.attrs[..])?; let attrs = serde::SerdeAttrib::try_from(&variant.attrs[..])?;
let variant_string = if let Some(renamed) = attrs.rename { let variant_string = if let Some(renamed) = attrs.rename {
renamed renamed.into_lit_str()
} else if let Some(rename_all) = container_attrs.rename_all { } else if let Some(rename_all) = container_attrs.rename_all {
let name = rename_all.apply_to_variant(&variant.ident.to_string()); let name = rename_all.apply_to_variant(&variant.ident.to_string());
syn::LitStr::new(&name, variant.ident.span()) syn::LitStr::new(&name, variant.ident.span())
@ -68,23 +64,8 @@ pub fn handle_enum(
syn::LitStr::new(&name.to_string(), name.span()) syn::LitStr::new(&name.to_string(), name.span())
}; };
if derives_default {
if let Some(attr) = variant.attrs.iter().find(|a| a.path.is_ident("default")) {
if let Some(default_value) = &default_value {
error!(attr => "multiple default values defined");
error!(default_value => "default previously defined here");
} else {
default_value = Some(variant_string.clone());
if let Some(span) = has_default_attrib {
error!(attr => "#[default] attribute in use with 'default' #[api] key");
error!(span, "'default' also defined here");
}
}
}
}
variants.extend(quote_spanned! { variant.ident.span() => variants.extend(quote_spanned! { variant.ident.span() =>
::proxmox_schema::EnumEntry { ::proxmox::api::schema::EnumEntry {
value: #variant_string, value: #variant_string,
description: #comment, description: #comment,
}, },
@ -93,24 +74,13 @@ pub fn handle_enum(
let name = &enum_ty.ident; let name = &enum_ty.ident;
let default_value = match default_value {
Some(value) => quote_spanned!(value.span() => .default(#value)),
None => TokenStream::new(),
};
Ok(quote_spanned! { name.span() => Ok(quote_spanned! { name.span() =>
#enum_ty #enum_ty
impl #name {
impl ::proxmox_schema::ApiType for #name { pub const API_SCHEMA: ::proxmox::api::schema::Schema =
const API_SCHEMA: ::proxmox_schema::Schema =
#schema #schema
.format(&::proxmox_schema::ApiStringFormat::Enum(&[#variants])) .format(&::proxmox::api::schema::ApiStringFormat::Enum(&[#variants]))
#default_value
.schema(); .schema();
} }
impl ::proxmox_schema::UpdaterType for #name {
type Updater = Option<Self>;
}
}) })
} }

View File

@ -11,7 +11,7 @@ use std::mem;
use anyhow::Error; use anyhow::Error;
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens}; use quote::{quote, quote_spanned};
use syn::ext::IdentExt; use syn::ext::IdentExt;
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::visit_mut::{self, VisitMut}; use syn::visit_mut::{self, VisitMut};
@ -20,53 +20,16 @@ use syn::Ident;
use super::{ObjectEntry, Schema, SchemaItem, SchemaObject}; use super::{ObjectEntry, Schema, SchemaItem, SchemaObject};
use crate::util::{self, FieldName, JSONObject, JSONValue, Maybe}; use crate::util::{self, FieldName, JSONObject, JSONValue, Maybe};
/// A return type in a schema can have an `optional` flag. Other than that it is just a regular
/// schema, but we also want to be able to reference external `ReturnType` values for this.
pub enum ReturnType {
Explicit(ReturnSchema),
Extern(syn::Expr),
}
impl ReturnType {
fn as_mut_schema(&mut self) -> Option<&mut Schema> {
match self {
ReturnType::Explicit(ReturnSchema { ref mut schema, .. }) => Some(schema),
_ => None,
}
}
}
impl TryFrom<JSONValue> for ReturnType {
type Error = syn::Error;
fn try_from(value: JSONValue) -> Result<Self, syn::Error> {
Ok(match value {
JSONValue::Object(obj) => ReturnType::Explicit(obj.try_into()?),
JSONValue::Expr(ext) => ReturnType::Extern(ext),
})
}
}
impl ReturnType {
fn to_schema(&self, ts: &mut TokenStream) -> Result<(), Error> {
match self {
ReturnType::Explicit(exp) => exp.to_schema(ts)?,
ReturnType::Extern(exp) => exp.to_tokens(ts),
}
Ok(())
}
}
/// A return type in a schema can have an `optional` flag. Other than that it is just a regular /// A return type in a schema can have an `optional` flag. Other than that it is just a regular
/// schema. /// schema.
pub struct ReturnSchema { pub struct ReturnType {
/// If optional, we store `Some(span)`, otherwise `None`. /// If optional, we store `Some(span)`, otherwise `None`.
optional: Option<Span>, optional: Option<Span>,
schema: Schema, schema: Schema,
} }
impl ReturnSchema { impl ReturnType {
fn to_schema(&self, ts: &mut TokenStream) -> Result<(), Error> { fn to_schema(&self, ts: &mut TokenStream) -> Result<(), Error> {
let optional = match self.optional { let optional = match self.optional {
Some(span) => quote_spanned! { span => true }, Some(span) => quote_spanned! { span => true },
@ -77,15 +40,23 @@ impl ReturnSchema {
self.schema.to_schema(&mut out)?; self.schema.to_schema(&mut out)?;
ts.extend(quote! { ts.extend(quote! {
::proxmox_schema::ReturnType::new( #optional , &#out ) ::proxmox::api::router::ReturnType::new( #optional , &#out )
}); });
Ok(()) Ok(())
} }
} }
/// To go from a `JSONObject` to a `ReturnSchema` we first extract the `optional` flag, then forward impl TryFrom<JSONValue> for ReturnType {
type Error = syn::Error;
fn try_from(value: JSONValue) -> Result<Self, syn::Error> {
Self::try_from(value.into_object("a return type definition")?)
}
}
/// To go from a `JSONObject` to a `ReturnType` we first extract the `optional` flag, then forward
/// to the `Schema` parser. /// to the `Schema` parser.
impl TryFrom<JSONObject> for ReturnSchema { impl TryFrom<JSONObject> for ReturnType {
type Error = syn::Error; type Error = syn::Error;
fn try_from(mut obj: JSONObject) -> Result<Self, syn::Error> { fn try_from(mut obj: JSONObject) -> Result<Self, syn::Error> {
@ -139,7 +110,7 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
let mut return_type: Option<ReturnType> = attribs let mut return_type: Option<ReturnType> = attribs
.remove("returns") .remove("returns")
.map(|ret| ret.try_into()) .map(|ret| ret.into_object("return schema definition")?.try_into())
.transpose()?; .transpose()?;
let access_setter = match attribs.remove("access") { let access_setter = match attribs.remove("access") {
@ -169,12 +140,6 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
.transpose()? .transpose()?
.unwrap_or(false); .unwrap_or(false);
let streaming: bool = attribs
.remove("streaming")
.map(TryFrom::try_from)
.transpose()?
.unwrap_or(false);
if !attribs.is_empty() { if !attribs.is_empty() {
error!( error!(
attribs.span(), attribs.span(),
@ -186,7 +151,7 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
let (doc_comment, doc_span) = util::get_doc_comments(&func.attrs)?; let (doc_comment, doc_span) = util::get_doc_comments(&func.attrs)?;
util::derive_descriptions( util::derive_descriptions(
&mut input_schema, &mut input_schema,
return_type.as_mut().and_then(ReturnType::as_mut_schema), return_type.as_mut().map(|rs| &mut rs.schema),
&doc_comment, &doc_comment,
doc_span, doc_span,
)?; )?;
@ -201,7 +166,6 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
&mut func, &mut func,
&mut wrapper_ts, &mut wrapper_ts,
&mut default_consts, &mut default_consts,
streaming,
)?; )?;
// input schema is done, let's give the method body a chance to extract default parameters: // input schema is done, let's give the method body a chance to extract default parameters:
@ -224,18 +188,17 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
returns_schema_setter = quote! { .returns(#inner) }; returns_schema_setter = quote! { .returns(#inner) };
} }
let api_handler = match (streaming, is_async) { let api_handler = if is_async {
(true, true) => quote! { ::proxmox_router::ApiHandler::StreamingAsync(&#api_func_name) }, quote! { ::proxmox::api::ApiHandler::Async(&#api_func_name) }
(true, false) => quote! { ::proxmox_router::ApiHandler::StreamingSync(&#api_func_name) }, } else {
(false, true) => quote! { ::proxmox_router::ApiHandler::Async(&#api_func_name) }, quote! { ::proxmox::api::ApiHandler::Sync(&#api_func_name) }
(false, false) => quote! { ::proxmox_router::ApiHandler::Sync(&#api_func_name) },
}; };
Ok(quote_spanned! { func.sig.span() => Ok(quote_spanned! { func.sig.span() =>
#input_schema_code #input_schema_code
#vis const #api_method_name: ::proxmox_router::ApiMethod = #vis const #api_method_name: ::proxmox::api::ApiMethod =
::proxmox_router::ApiMethod::new_full( ::proxmox::api::ApiMethod::new_full(
&#api_handler, &#api_handler,
#input_schema_parameter, #input_schema_parameter,
) )
@ -283,11 +246,10 @@ fn check_input_type(input: &syn::FnArg) -> Result<(&syn::PatType, &syn::PatIdent
fn handle_function_signature( fn handle_function_signature(
input_schema: &mut Schema, input_schema: &mut Schema,
_return_type: &mut Option<ReturnType>, return_type: &mut Option<ReturnType>,
func: &mut syn::ItemFn, func: &mut syn::ItemFn,
wrapper_ts: &mut TokenStream, wrapper_ts: &mut TokenStream,
default_consts: &mut TokenStream, default_consts: &mut TokenStream,
streaming: bool,
) -> Result<Ident, Error> { ) -> Result<Ident, Error> {
let sig = &func.sig; let sig = &func.sig;
let is_async = sig.asyncness.is_some(); let is_async = sig.asyncness.is_some();
@ -310,7 +272,7 @@ fn handle_function_signature(
// For any named type which exists on the function signature... // For any named type which exists on the function signature...
if let Some(entry) = input_schema.find_obj_property_by_ident_mut(&pat.ident.to_string()) { if let Some(entry) = input_schema.find_obj_property_by_ident_mut(&pat.ident.to_string()) {
// try to infer the type in the schema if it is not specified explicitly: // try to infer the type in the schema if it is not specified explicitly:
let is_option = util::infer_type(&mut entry.schema, &pat_type.ty)?; let is_option = util::infer_type(&mut entry.schema, &*pat_type.ty)?;
let has_default = entry.schema.find_schema_property("default").is_some(); let has_default = entry.schema.find_schema_property("default").is_some();
if !is_option && entry.optional.expect_bool() && !has_default { if !is_option && entry.optional.expect_bool() && !has_default {
error!(pat_type => "optional types need a default or be an Option<T>"); error!(pat_type => "optional types need a default or be an Option<T>");
@ -365,7 +327,7 @@ fn handle_function_signature(
// Found an explicit parameter: extract it: // Found an explicit parameter: extract it:
ParameterType::Normal(NormalParameter { ParameterType::Normal(NormalParameter {
ty: &pat_type.ty, ty: &pat_type.ty,
entry, entry: &entry,
}) })
} else if is_api_method_type(&pat_type.ty) { } else if is_api_method_type(&pat_type.ty) {
if api_method_param.is_some() { if api_method_param.is_some() {
@ -416,14 +378,13 @@ fn handle_function_signature(
*/ */
create_wrapper_function( create_wrapper_function(
//input_schema, input_schema,
//return_type, return_type,
param_list, param_list,
func, func,
wrapper_ts, wrapper_ts,
default_consts, default_consts,
is_async, is_async,
streaming,
) )
} }
@ -474,14 +435,13 @@ fn is_value_type(ty: &syn::Type) -> bool {
} }
fn create_wrapper_function( fn create_wrapper_function(
//_input_schema: &Schema, _input_schema: &Schema,
//_returns_schema: &Option<ReturnType>, _returns_schema: &Option<ReturnType>,
param_list: Vec<(FieldName, ParameterType)>, param_list: Vec<(FieldName, ParameterType)>,
func: &syn::ItemFn, func: &syn::ItemFn,
wrapper_ts: &mut TokenStream, wrapper_ts: &mut TokenStream,
default_consts: &mut TokenStream, default_consts: &mut TokenStream,
is_async: bool, is_async: bool,
streaming: bool,
) -> Result<Ident, Error> { ) -> Result<Ident, Error> {
let api_func_name = Ident::new( let api_func_name = Ident::new(
&format!("api_function_{}", &func.sig.ident), &format!("api_function_{}", &func.sig.ident),
@ -523,83 +483,45 @@ fn create_wrapper_function(
_ => Some(quote!(?)), _ => Some(quote!(?)),
}; };
let body = if streaming { let body = quote! {
quote! { if let ::serde_json::Value::Object(ref mut input_map) = &mut input_params {
if let ::serde_json::Value::Object(ref mut input_map) = &mut input_params { #body
#body Ok(::serde_json::to_value(#func_name(#args) #await_keyword #question_mark)?)
let res = #func_name(#args) #await_keyword #question_mark; } else {
let res: ::std::boxed::Box<dyn ::proxmox_router::SerializableReturn + Send> = ::std::boxed::Box::new(res); ::anyhow::bail!("api function wrapper called with a non-object json value");
Ok(res)
} else {
::anyhow::bail!("api function wrapper called with a non-object json value");
}
}
} else {
quote! {
if let ::serde_json::Value::Object(ref mut input_map) = &mut input_params {
#body
Ok(::serde_json::to_value(#func_name(#args) #await_keyword #question_mark)?)
} else {
::anyhow::bail!("api function wrapper called with a non-object json value");
}
} }
}; };
match (streaming, is_async) { if is_async {
(true, true) => { wrapper_ts.extend(quote! {
wrapper_ts.extend(quote! { fn #api_func_name<'a>(
fn #api_func_name<'a>( mut input_params: ::serde_json::Value,
mut input_params: ::serde_json::Value, api_method_param: &'static ::proxmox::api::ApiMethod,
api_method_param: &'static ::proxmox_router::ApiMethod, rpc_env_param: &'a mut dyn ::proxmox::api::RpcEnvironment,
rpc_env_param: &'a mut dyn ::proxmox_router::RpcEnvironment, ) -> ::proxmox::api::ApiFuture<'a> {
) -> ::proxmox_router::StreamingApiFuture<'a> { //async fn func<'a>(
::std::boxed::Box::pin(async move { #body }) // mut input_params: ::serde_json::Value,
} // api_method_param: &'static ::proxmox::api::ApiMethod,
}); // rpc_env_param: &'a mut dyn ::proxmox::api::RpcEnvironment,
} //) -> ::std::result::Result<::serde_json::Value, ::anyhow::Error> {
(true, false) => { // #body
wrapper_ts.extend(quote! { //}
fn #api_func_name( //::std::boxed::Box::pin(async move {
mut input_params: ::serde_json::Value, // func(input_params, api_method_param, rpc_env_param).await
api_method_param: &::proxmox_router::ApiMethod, //})
rpc_env_param: &mut dyn ::proxmox_router::RpcEnvironment, ::std::boxed::Box::pin(async move { #body })
) -> ::std::result::Result<::std::boxed::Box<dyn ::proxmox_router::SerializableReturn + Send>, ::anyhow::Error> { }
#body });
} } else {
}); wrapper_ts.extend(quote! {
} fn #api_func_name(
(false, true) => { mut input_params: ::serde_json::Value,
wrapper_ts.extend(quote! { api_method_param: &::proxmox::api::ApiMethod,
fn #api_func_name<'a>( rpc_env_param: &mut dyn ::proxmox::api::RpcEnvironment,
mut input_params: ::serde_json::Value, ) -> ::std::result::Result<::serde_json::Value, ::anyhow::Error> {
api_method_param: &'static ::proxmox_router::ApiMethod, #body
rpc_env_param: &'a mut dyn ::proxmox_router::RpcEnvironment, }
) -> ::proxmox_router::ApiFuture<'a> { });
//async fn func<'a>(
// mut input_params: ::serde_json::Value,
// api_method_param: &'static ::proxmox_router::ApiMethod,
// rpc_env_param: &'a mut dyn ::proxmox_router::RpcEnvironment,
//) -> ::std::result::Result<::serde_json::Value, ::anyhow::Error> {
// #body
//}
//::std::boxed::Box::pin(async move {
// func(input_params, api_method_param, rpc_env_param).await
//})
::std::boxed::Box::pin(async move { #body })
}
});
}
(false, false) => {
wrapper_ts.extend(quote! {
fn #api_func_name(
mut input_params: ::serde_json::Value,
api_method_param: &::proxmox_router::ApiMethod,
rpc_env_param: &mut dyn ::proxmox_router::RpcEnvironment,
) -> ::std::result::Result<::serde_json::Value, ::anyhow::Error> {
#body
}
});
}
} }
Ok(api_func_name) Ok(api_func_name)
@ -616,7 +538,7 @@ fn extract_normal_parameter(
) -> Result<(), Error> { ) -> Result<(), Error> {
let span = name_span; // renamed during refactorization let span = name_span; // renamed during refactorization
let name_str = syn::LitStr::new(name.as_str(), span); let name_str = syn::LitStr::new(name.as_str(), span);
let arg_name = Ident::new(&format!("input_arg_{}", name.as_ident()), span); let arg_name = Ident::new(&format!("input_arg_{}", name.as_ident().to_string()), span);
let default_value = param.entry.schema.find_schema_property("default"); let default_value = param.entry.schema.find_schema_property("default");
@ -699,7 +621,7 @@ fn extract_normal_parameter(
let ty = param.ty; let ty = param.ty;
body.extend(quote_spanned! { span => body.extend(quote_spanned! { span =>
let #arg_name = <#ty as ::serde::Deserialize>::deserialize( let #arg_name = <#ty as ::serde::Deserialize>::deserialize(
::proxmox_schema::de::ExtractValueDeserializer::try_new( ::proxmox::api::de::ExtractValueDeserializer::try_new(
input_map, input_map,
#schema_ref, #schema_ref,
) )
@ -752,10 +674,10 @@ fn serialize_input_schema(
input_schema.to_typed_schema(&mut ts)?; input_schema.to_typed_schema(&mut ts)?;
return Ok(( return Ok((
quote_spanned! { func_sig_span => quote_spanned! { func_sig_span =>
pub const #input_schema_name: ::proxmox_schema::ObjectSchema = #ts; pub const #input_schema_name: ::proxmox::api::schema::ObjectSchema = #ts;
}, },
quote_spanned! { func_sig_span => quote_spanned! { func_sig_span =>
::proxmox_schema::ParameterSchema::Object(&#input_schema_name) ::proxmox::api::schema::ParameterSchema::Object(&#input_schema_name)
}, },
)); ));
} }
@ -807,7 +729,7 @@ fn serialize_input_schema(
( (
quote_spanned!(func_sig_span => quote_spanned!(func_sig_span =>
const #inner_schema_name: ::proxmox_schema::Schema = #obj_schema; const #inner_schema_name: ::proxmox::api::schema::Schema = #obj_schema;
), ),
quote_spanned!(func_sig_span => &#inner_schema_name,), quote_spanned!(func_sig_span => &#inner_schema_name,),
) )
@ -820,8 +742,8 @@ fn serialize_input_schema(
quote_spanned! { func_sig_span => quote_spanned! { func_sig_span =>
#inner_schema #inner_schema
pub const #input_schema_name: ::proxmox_schema::AllOfSchema = pub const #input_schema_name: ::proxmox::api::schema::AllOfSchema =
::proxmox_schema::AllOfSchema::new( ::proxmox::api::schema::AllOfSchema::new(
#description, #description,
&[ &[
#inner_schema_ref #inner_schema_ref
@ -830,7 +752,7 @@ fn serialize_input_schema(
); );
}, },
quote_spanned! { func_sig_span => quote_spanned! { func_sig_span =>
::proxmox_schema::ParameterSchema::AllOf(&#input_schema_name) ::proxmox::api::schema::ParameterSchema::AllOf(&#input_schema_name)
}, },
)) ))
} }

View File

@ -19,7 +19,6 @@ use syn::{Expr, ExprPath, Ident};
use crate::util::{FieldName, JSONObject, JSONValue, Maybe}; use crate::util::{FieldName, JSONObject, JSONValue, Maybe};
mod attributes;
mod enums; mod enums;
mod method; mod method;
mod structs; mod structs;
@ -147,9 +146,9 @@ impl Schema {
/// Create the token stream for a reference schema (`ExternType` or `ExternSchema`). /// Create the token stream for a reference schema (`ExternType` or `ExternSchema`).
fn to_schema_reference(&self) -> Option<TokenStream> { fn to_schema_reference(&self) -> Option<TokenStream> {
match &self.item { match &self.item {
SchemaItem::ExternType(path) => Some( SchemaItem::ExternType(path) => {
quote_spanned! { path.span() => &<#path as ::proxmox_schema::ApiType>::API_SCHEMA }, Some(quote_spanned! { path.span() => &#path::API_SCHEMA })
), }
SchemaItem::ExternSchema(path) => Some(quote_spanned! { path.span() => &#path }), SchemaItem::ExternSchema(path) => Some(quote_spanned! { path.span() => &#path }),
_ => None, _ => None,
} }
@ -199,12 +198,6 @@ impl Schema {
.and_then(|obj| obj.find_property_by_ident_mut(key)) .and_then(|obj| obj.find_property_by_ident_mut(key))
} }
fn remove_obj_property_by_ident(&mut self, key: &str) -> bool {
self.as_object_mut()
.expect("tried to remove object property on non-object schema")
.remove_property_by_ident(key)
}
// FIXME: Should we turn the property list into a map? We used to have no need to find keys in // FIXME: Should we turn the property list into a map? We used to have no need to find keys in
// it, but we do now... // it, but we do now...
fn find_schema_property(&self, key: &str) -> Option<&syn::Expr> { fn find_schema_property(&self, key: &str) -> Option<&syn::Expr> {
@ -323,31 +316,31 @@ impl SchemaItem {
SchemaItem::Null(span) => { SchemaItem::Null(span) => {
let description = check_description()?; let description = check_description()?;
ts.extend(quote_spanned! { *span => ts.extend(quote_spanned! { *span =>
::proxmox_schema::NullSchema::new(#description) ::proxmox::api::schema::NullSchema::new(#description)
}); });
} }
SchemaItem::Boolean(span) => { SchemaItem::Boolean(span) => {
let description = check_description()?; let description = check_description()?;
ts.extend(quote_spanned! { *span => ts.extend(quote_spanned! { *span =>
::proxmox_schema::BooleanSchema::new(#description) ::proxmox::api::schema::BooleanSchema::new(#description)
}); });
} }
SchemaItem::Integer(span) => { SchemaItem::Integer(span) => {
let description = check_description()?; let description = check_description()?;
ts.extend(quote_spanned! { *span => ts.extend(quote_spanned! { *span =>
::proxmox_schema::IntegerSchema::new(#description) ::proxmox::api::schema::IntegerSchema::new(#description)
}); });
} }
SchemaItem::Number(span) => { SchemaItem::Number(span) => {
let description = check_description()?; let description = check_description()?;
ts.extend(quote_spanned! { *span => ts.extend(quote_spanned! { *span =>
::proxmox_schema::NumberSchema::new(#description) ::proxmox::api::schema::NumberSchema::new(#description)
}); });
} }
SchemaItem::String(span) => { SchemaItem::String(span) => {
let description = check_description()?; let description = check_description()?;
ts.extend(quote_spanned! { *span => ts.extend(quote_spanned! { *span =>
::proxmox_schema::StringSchema::new(#description) ::proxmox::api::schema::StringSchema::new(#description)
}); });
} }
SchemaItem::Object(obj) => { SchemaItem::Object(obj) => {
@ -355,7 +348,7 @@ impl SchemaItem {
let mut elems = TokenStream::new(); let mut elems = TokenStream::new();
obj.to_schema_inner(&mut elems)?; obj.to_schema_inner(&mut elems)?;
ts.extend(quote_spanned! { obj.span => ts.extend(quote_spanned! { obj.span =>
::proxmox_schema::ObjectSchema::new(#description, &[#elems]) ::proxmox::api::schema::ObjectSchema::new(#description, &[#elems])
}); });
} }
SchemaItem::Array(array) => { SchemaItem::Array(array) => {
@ -363,7 +356,7 @@ impl SchemaItem {
let mut items = TokenStream::new(); let mut items = TokenStream::new();
array.to_schema(&mut items)?; array.to_schema(&mut items)?;
ts.extend(quote_spanned! { array.span => ts.extend(quote_spanned! { array.span =>
::proxmox_schema::ArraySchema::new(#description, &#items) ::proxmox::api::schema::ArraySchema::new(#description, &#items)
}); });
} }
SchemaItem::ExternType(path) => { SchemaItem::ExternType(path) => {
@ -375,7 +368,7 @@ impl SchemaItem {
error!(description => "description not allowed on external type"); error!(description => "description not allowed on external type");
} }
ts.extend(quote_spanned! { path.span() => <#path as ::proxmox_schema::ApiType>::API_SCHEMA }); ts.extend(quote_spanned! { path.span() => #path::API_SCHEMA });
return Ok(true); return Ok(true);
} }
SchemaItem::ExternSchema(path) => { SchemaItem::ExternSchema(path) => {
@ -443,7 +436,7 @@ pub enum OptionType {
/// An updater type uses its "base" type's field's updaters to determine whether the field is /// An updater type uses its "base" type's field's updaters to determine whether the field is
/// supposed to be an option. /// supposed to be an option.
Updater(Box<syn::Type>), Updater(syn::Type),
} }
impl OptionType { impl OptionType {
@ -465,7 +458,7 @@ impl From<bool> for OptionType {
impl From<syn::Type> for OptionType { impl From<syn::Type> for OptionType {
fn from(ty: syn::Type) -> Self { fn from(ty: syn::Type) -> Self {
OptionType::Updater(Box::new(ty)) OptionType::Updater(ty)
} }
} }
@ -473,7 +466,9 @@ impl ToTokens for OptionType {
fn to_tokens(&self, tokens: &mut TokenStream) { fn to_tokens(&self, tokens: &mut TokenStream) {
match self { match self {
OptionType::Bool(b) => b.to_tokens(tokens), OptionType::Bool(b) => b.to_tokens(tokens),
OptionType::Updater(_) => tokens.extend(quote! { true }), OptionType::Updater(ty) => tokens.extend(quote! {
<#ty as ::proxmox::api::schema::Updatable>::UPDATER_IS_OPTION
}),
} }
} }
} }
@ -649,20 +644,6 @@ impl SchemaObject {
.find(|p| p.name.as_ident_str() == key) .find(|p| p.name.as_ident_str() == key)
} }
fn remove_property_by_ident(&mut self, key: &str) -> bool {
match self
.properties_
.iter()
.position(|p| p.name.as_ident_str() == key)
{
Some(idx) => {
self.properties_.remove(idx);
true
}
None => false,
}
}
fn extend_properties(&mut self, new_fields: Vec<ObjectEntry>) { fn extend_properties(&mut self, new_fields: Vec<ObjectEntry>) {
self.properties_.extend(new_fields); self.properties_.extend(new_fields);
self.sort_properties(); self.sort_properties();

View File

@ -18,7 +18,6 @@ use anyhow::Error;
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2::{Ident, Span, TokenStream};
use quote::quote_spanned; use quote::quote_spanned;
use super::attributes::UpdaterFieldAttributes;
use super::Schema; use super::Schema;
use crate::api::{self, ObjectEntry, SchemaItem}; use crate::api::{self, ObjectEntry, SchemaItem};
use crate::serde; use crate::serde;
@ -59,15 +58,7 @@ fn handle_unit_struct(attribs: JSONObject, stru: syn::ItemStruct) -> Result<Toke
get_struct_description(&mut schema, &stru)?; get_struct_description(&mut schema, &stru)?;
let name = &stru.ident; finish_schema(schema, &stru, &stru.ident)
let mut schema = finish_schema(schema, &stru, name)?;
schema.extend(quote_spanned! { name.span() =>
impl ::proxmox_schema::UpdaterType for #name {
type Updater = Option<Self>;
}
});
Ok(schema)
} }
fn finish_schema( fn finish_schema(
@ -83,9 +74,8 @@ fn finish_schema(
Ok(quote_spanned! { name.span() => Ok(quote_spanned! { name.span() =>
#stru #stru
impl #name {
impl ::proxmox_schema::ApiType for #name { pub const API_SCHEMA: ::proxmox::api::schema::Schema = #schema;
const API_SCHEMA: ::proxmox_schema::Schema = #schema;
} }
}) })
} }
@ -169,7 +159,7 @@ fn handle_regular_struct(
.ok_or_else(|| format_err!(field => "field without name?"))?; .ok_or_else(|| format_err!(field => "field without name?"))?;
if let Some(renamed) = attrs.rename { if let Some(renamed) = attrs.rename {
(renamed.value(), ident.span()) (renamed.into_str(), ident.span())
} else if let Some(rename_all) = container_attrs.rename_all { } else if let Some(rename_all) = container_attrs.rename_all {
let name = rename_all.apply_to_field(&ident.to_string()); let name = rename_all.apply_to_field(&ident.to_string());
(name, ident.span()) (name, ident.span())
@ -271,16 +261,7 @@ fn handle_regular_struct(
} }
}); });
if derive { if derive {
let updater = derive_updater(stru.clone(), schema.clone(), &mut stru)?; derive_updater(stru.clone(), schema.clone(), &mut stru)?
// make sure we don't leave #[updater] attributes on the original struct:
if let syn::Fields::Named(fields) = &mut stru.fields {
for field in &mut fields.named {
let _ = UpdaterFieldAttributes::from_attributes(&mut field.attrs);
}
}
updater
} else { } else {
TokenStream::new() TokenStream::new()
} }
@ -336,7 +317,7 @@ fn finish_all_of_struct(
( (
quote_spanned!(name.span() => quote_spanned!(name.span() =>
const INNER_API_SCHEMA: ::proxmox_schema::Schema = #obj_schema; const INNER_API_SCHEMA: ::proxmox::api::schema::Schema = #obj_schema;
), ),
quote_spanned!(name.span() => &Self::INNER_API_SCHEMA,), quote_spanned!(name.span() => &Self::INNER_API_SCHEMA,),
) )
@ -347,14 +328,10 @@ fn finish_all_of_struct(
Ok(quote_spanned!(name.span() => Ok(quote_spanned!(name.span() =>
#stru #stru
impl #name { impl #name {
#inner_schema #inner_schema
} pub const API_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox::api::schema::AllOfSchema::new(
impl ::proxmox_schema::ApiType for #name {
const API_SCHEMA: ::proxmox_schema::Schema =
::proxmox_schema::AllOfSchema::new(
#description, #description,
&[ &[
#inner_schema_ref #inner_schema_ref
@ -401,35 +378,77 @@ fn derive_updater(
mut schema: Schema, mut schema: Schema,
original_struct: &mut syn::ItemStruct, original_struct: &mut syn::ItemStruct,
) -> Result<TokenStream, Error> { ) -> Result<TokenStream, Error> {
let original_name = &original_struct.ident;
stru.ident = Ident::new(&format!("{}Updater", stru.ident), stru.ident.span()); stru.ident = Ident::new(&format!("{}Updater", stru.ident), stru.ident.span());
if !util::derives_trait(&original_struct.attrs, "Default") { if !util::derived_items(&original_struct.attrs).any(|p| p.is_ident("Default")) {
stru.attrs.push(util::make_derive_attribute( original_struct.attrs.push(util::make_derive_attribute(
Span::call_site(), Span::call_site(),
quote::quote! { Default }, quote::quote! { Default },
)); ));
} }
original_struct.attrs.push(util::make_derive_attribute(
Span::call_site(),
quote::quote! { ::proxmox::api::schema::Updatable },
));
let updater_name = &stru.ident; let updater_name = &stru.ident;
let updater_name_str = syn::LitStr::new(&updater_name.to_string(), updater_name.span());
original_struct.attrs.push(util::make_attribute(
Span::call_site(),
util::make_path(Span::call_site(), false, &["updatable"]),
quote::quote! { (updater = #updater_name_str) },
));
let mut all_of_schemas = TokenStream::new(); let mut all_of_schemas = TokenStream::new();
let mut is_empty_impl = TokenStream::new(); let mut is_empty_impl = TokenStream::new();
if let syn::Fields::Named(fields) = &mut stru.fields { if let syn::Fields::Named(fields) = &mut stru.fields {
for mut field in std::mem::take(&mut fields.named) { for field in &mut fields.named {
match handle_updater_field( let field_name = field.ident.as_ref().expect("unnamed field in FieldsNamed");
&mut field, let field_name_string = field_name.to_string();
&mut schema,
&mut all_of_schemas, let field_schema = match schema.find_obj_property_by_ident_mut(&field_name_string) {
&mut is_empty_impl, Some(obj) => obj,
) { None => {
Ok(FieldAction::Keep) => fields.named.push(field), error!(
Ok(FieldAction::Skip) => (), field_name.span(),
Err(err) => { "failed to find schema entry for {:?}", field_name_string,
crate::add_error(err); );
fields.named.push(field); continue;
} }
};
field_schema.optional = field.ty.clone().into();
let span = Span::call_site();
let updater = syn::TypePath {
qself: Some(syn::QSelf {
lt_token: syn::token::Lt { spans: [span] },
ty: Box::new(field.ty.clone()),
position: 4, // 'Updater' is the 4th item in the 'segments' below
as_token: Some(syn::token::As { span }),
gt_token: syn::token::Gt { spans: [span] },
}),
path: util::make_path(
span,
true,
&["proxmox", "api", "schema", "Updatable", "Updater"],
),
};
field.ty = syn::Type::Path(updater);
if field_schema.flatten_in_struct {
let updater_ty = &field.ty;
all_of_schemas.extend(quote::quote! {&#updater_ty::API_SCHEMA,});
} }
if !is_empty_impl.is_empty() {
is_empty_impl.extend(quote::quote! { && });
}
is_empty_impl.extend(quote::quote! {
self.#field_name.is_empty()
});
} }
} }
@ -440,105 +459,15 @@ fn derive_updater(
}; };
if !is_empty_impl.is_empty() { if !is_empty_impl.is_empty() {
output.extend(quote::quote!( output = quote::quote!(
impl ::proxmox_schema::Updater for #updater_name { #output
impl ::proxmox::api::schema::Updater for #updater_name {
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool {
#is_empty_impl #is_empty_impl
} }
} }
)); );
} }
output.extend(quote::quote!(
impl ::proxmox_schema::UpdaterType for #original_name {
type Updater = #updater_name;
}
));
Ok(output) Ok(output)
} }
enum FieldAction {
Keep,
Skip,
}
fn handle_updater_field(
field: &mut syn::Field,
schema: &mut Schema,
all_of_schemas: &mut TokenStream,
is_empty_impl: &mut TokenStream,
) -> Result<FieldAction, syn::Error> {
let updater_attrs = UpdaterFieldAttributes::from_attributes(&mut field.attrs);
let field_name = field.ident.as_ref().expect("unnamed field in FieldsNamed");
let field_name_string = field_name.to_string();
if updater_attrs.skip() {
if !schema.remove_obj_property_by_ident(&field_name_string) {
bail!(
field_name.span(),
"failed to find schema entry for {:?}",
field_name_string,
);
}
return Ok(FieldAction::Skip);
}
let field_schema = match schema.find_obj_property_by_ident_mut(&field_name_string) {
Some(obj) => obj,
None => {
bail!(
field_name.span(),
"failed to find schema entry for {:?}",
field_name_string,
);
}
};
let span = Span::call_site();
field_schema.optional = field.ty.clone().into();
let updater = match updater_attrs.ty() {
Some(ty) => ty.clone(),
None => {
syn::TypePath {
qself: Some(syn::QSelf {
lt_token: syn::token::Lt { spans: [span] },
ty: Box::new(field.ty.clone()),
position: 2, // 'Updater' is item index 2 in the 'segments' below
as_token: Some(syn::token::As { span }),
gt_token: syn::token::Gt { spans: [span] },
}),
path: util::make_path(span, true, &["proxmox_schema", "UpdaterType", "Updater"]),
}
}
};
updater_attrs.replace_serde_attributes(&mut field.attrs);
// we also need to update the schema to point to the updater's schema for `type: Foo` entries
if let SchemaItem::ExternType(path) = &mut field_schema.schema.item {
*path = syn::ExprPath {
attrs: Vec::new(),
qself: updater.qself.clone(),
path: updater.path.clone(),
};
}
field.ty = syn::Type::Path(updater);
if field_schema.flatten_in_struct {
let updater_ty = &field.ty;
all_of_schemas
.extend(quote::quote! {&<#updater_ty as ::proxmox_schema::ApiType>::API_SCHEMA,});
}
if !is_empty_impl.is_empty() {
is_empty_impl.extend(quote::quote! { && });
}
is_empty_impl.extend(quote::quote! {
self.#field_name.is_empty()
});
Ok(FieldAction::Keep)
}

View File

@ -69,7 +69,7 @@ fn router_do(item: TokenStream) -> Result<TokenStream, Error> {
``` ```
# use proxmox_api_macro::api; # use proxmox_api_macro::api;
# use proxmox_router::{ApiMethod, RpcEnvironment}; # use proxmox::api::{ApiMethod, RpcEnvironment};
use anyhow::Error; use anyhow::Error;
use serde_json::Value; use serde_json::Value;
@ -103,7 +103,7 @@ fn router_do(item: TokenStream) -> Result<TokenStream, Error> {
}, },
"CSRFPreventionToken": { "CSRFPreventionToken": {
type: String, type: String,
description: "Cross Site Request Forgery Prevention Token.", description: "Cross Site Request Forgerty Prevention Token.",
}, },
}, },
}, },
@ -178,19 +178,19 @@ fn router_do(item: TokenStream) -> Result<TokenStream, Error> {
```no_run ```no_run
# struct RenamedStruct; # struct RenamedStruct;
impl RenamedStruct { impl RenamedStruct {
pub const API_SCHEMA: &'static ::proxmox_schema::Schema = pub const API_SCHEMA: &'static ::proxmox::api::schema::Schema =
&::proxmox_schema::ObjectSchema::new( &::proxmox::api::schema::ObjectSchema::new(
"An example of a struct with renamed fields.", "An example of a struct with renamed fields.",
&[ &[
( (
"test-string", "test-string",
false, false,
&::proxmox_schema::StringSchema::new("A test string.").schema(), &::proxmox::api::schema::StringSchema::new("A test string.").schema(),
), ),
( (
"SomeOther", "SomeOther",
true, true,
&::proxmox_schema::StringSchema::new( &::proxmox::api::schema::StringSchema::new(
"An optional auto-derived value for testing:", "An optional auto-derived value for testing:",
) )
.schema(), .schema(),
@ -231,7 +231,7 @@ fn router_do(item: TokenStream) -> Result<TokenStream, Error> {
# Deriving an `Updater`: # Deriving an `Updater`:
An "Updater" struct can be generated automatically for a type. This affects the `UpdaterType` An "Updater" struct can be generated automatically for a type. This affects the `Updatable`
trait implementation generated, as it will set the associated trait implementation generated, as it will set the associated
`type Updater = TheDerivedUpdater`. `type Updater = TheDerivedUpdater`.
@ -239,13 +239,9 @@ fn router_do(item: TokenStream) -> Result<TokenStream, Error> {
This is only supported for `struct`s with named fields and will generate a new `struct` whose This is only supported for `struct`s with named fields and will generate a new `struct` whose
name is suffixed with `Updater` containing the `Updater` types of each field as a member. name is suffixed with `Updater` containing the `Updater` types of each field as a member.
For the updater, the following additional attributes can be used: Additionally the `#[updater(fixed)]` option is available to make it illegal for an updater to
modify a field (generating an error if it is set), while still allowing it to be used to create
- `#[updater(skip)]`: skip the field entirely in the updater a new object via the `build_from()` method.
- `#[updater(type = "DifferentType")]`: use `DifferentType` instead of `Option<OriginalType>`
for the updater field.
- `#[updater(serde(<content>))]`: *replace* the `#[serde]` attributes in the generated updater
with `<content`>. This can be used to have different `skip_serializing_if` serde attributes.
```ignore ```ignore
#[api] #[api]
@ -269,10 +265,10 @@ fn router_do(item: TokenStream) -> Result<TokenStream, Error> {
#[api] #[api]
/// An example of a simple struct type. /// An example of a simple struct type.
pub struct MyTypeUpdater { pub struct MyTypeUpdater {
one: Option<String>, // really <String as UpdaterType>::Updater one: Option<String>, // really <String as Updatable>::Updater
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
opt: Option<String>, // really <Option<String> as UpdaterType>::Updater opt: Option<String>, // really <Option<String> as Updatable>::Updater
} }
impl Updater for MyTypeUpdater { impl Updater for MyTypeUpdater {
@ -281,8 +277,36 @@ fn router_do(item: TokenStream) -> Result<TokenStream, Error> {
} }
} }
impl UpdaterType for MyType { impl Updatable for MyType {
type Updater = MyTypeUpdater; type Updater = MyTypeUpdater;
fn update_from<T>(&mut self, from: MyTypeUpdater, delete: &[T]) -> Result<(), Error>
where
T: AsRef<str>,
{
for delete in delete {
match delete.as_ref() {
"opt" => { self.opt = None; }
_ => (),
}
}
self.one.update_from(from.one)?;
self.opt.update_from(from.opt)?;
Ok(())
}
fn try_build_from(from: MyTypeUpdater) -> Result<Self, Error> {
Ok(Self {
// This amounts to `from.one.ok_or_else("cannot build from None value")?`
one: Updatable::try_build_from(from.one)
.map_err(|err| format_err!("failed to build value for field 'one': {}", err))?,
// This amounts to `from.opt`
opt: Updatable::try_build_from(from.opt)
.map_err(|err| format_err!("failed to build value for field 'opt': {}", err))?,
})
}
} }
``` ```
@ -296,21 +320,17 @@ pub fn api(attr: TokenStream_1, item: TokenStream_1) -> TokenStream_1 {
/// This is a dummy derive macro actually handled by `#[api]`! /// This is a dummy derive macro actually handled by `#[api]`!
#[doc(hidden)] #[doc(hidden)]
#[proc_macro_derive(Updater, attributes(updater, serde))] #[proc_macro_derive(Updater, attributes(updater, updatable, serde))]
pub fn derive_updater(_item: TokenStream_1) -> TokenStream_1 { pub fn derive_updater(_item: TokenStream_1) -> TokenStream_1 {
TokenStream_1::new() TokenStream_1::new()
} }
/// Create the default `UpdaterType` implementation as an `Option<Self>`. /// Create the default `Updatable` implementation from an `Option<Self>`.
#[proc_macro_derive(UpdaterType, attributes(updater_type, serde))] #[proc_macro_derive(Updatable, attributes(updatable, serde))]
pub fn derive_updater_type(item: TokenStream_1) -> TokenStream_1 { pub fn derive_updatable(item: TokenStream_1) -> TokenStream_1 {
let _error_guard = init_local_error(); let _error_guard = init_local_error();
let item: TokenStream = item.into(); let item: TokenStream = item.into();
handle_error( handle_error(item.clone(), updater::updatable(item).map_err(Error::from)).into()
item.clone(),
updater::updater_type(item).map_err(Error::from),
)
.into()
} }
thread_local!(static NON_FATAL_ERRORS: RefCell<Option<TokenStream>> = RefCell::new(None)); thread_local!(static NON_FATAL_ERRORS: RefCell<Option<TokenStream>> = RefCell::new(None));

View File

@ -5,10 +5,9 @@
use std::convert::TryFrom; use std::convert::TryFrom;
use crate::util::AttrArgs; use crate::util::{AttrArgs, FieldName};
/// Serde name types. /// Serde name types.
#[allow(clippy::enum_variant_names)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RenameAll { pub enum RenameAll {
LowerCase, LowerCase,
@ -159,51 +158,44 @@ impl TryFrom<&[syn::Attribute]> for ContainerAttrib {
/// `serde` field/variant attributes we support /// `serde` field/variant attributes we support
#[derive(Default)] #[derive(Default)]
pub struct SerdeAttrib { pub struct SerdeAttrib {
pub rename: Option<syn::LitStr>, pub rename: Option<FieldName>,
pub flatten: bool, pub flatten: bool,
} }
impl SerdeAttrib {
pub fn parse_attribute(&mut self, attrib: &syn::Attribute) -> Result<(), syn::Error> {
use syn::{Meta, NestedMeta};
if !attrib.path.is_ident("serde") {
return Ok(());
}
let args: AttrArgs = syn::parse2(attrib.tokens.clone())?;
for arg in args.args {
match arg {
NestedMeta::Meta(Meta::NameValue(var)) if var.path.is_ident("rename") => {
match var.lit {
syn::Lit::Str(rename) => {
if self.rename.is_some() && self.rename.as_ref() != Some(&rename) {
error!(&rename => "multiple conflicting 'rename' attributes");
}
self.rename = Some(rename);
}
_ => error!(var.lit => "'rename' value must be a string literal"),
}
}
NestedMeta::Meta(Meta::Path(path)) if path.is_ident("flatten") => {
self.flatten = true;
}
_ => continue,
}
}
Ok(())
}
}
impl TryFrom<&[syn::Attribute]> for SerdeAttrib { impl TryFrom<&[syn::Attribute]> for SerdeAttrib {
type Error = syn::Error; type Error = syn::Error;
fn try_from(attributes: &[syn::Attribute]) -> Result<Self, syn::Error> { fn try_from(attributes: &[syn::Attribute]) -> Result<Self, syn::Error> {
use syn::{Meta, NestedMeta};
let mut this: Self = Default::default(); let mut this: Self = Default::default();
for attrib in attributes { for attrib in attributes {
this.parse_attribute(attrib)?; if !attrib.path.is_ident("serde") {
continue;
}
let args: AttrArgs = syn::parse2(attrib.tokens.clone())?;
for arg in args.args {
match arg {
NestedMeta::Meta(Meta::NameValue(var)) if var.path.is_ident("rename") => {
match var.lit {
syn::Lit::Str(lit) => {
let rename = FieldName::from(&lit);
if this.rename.is_some() && this.rename.as_ref() != Some(&rename) {
error!(lit => "multiple conflicting 'rename' attributes");
}
this.rename = Some(rename);
}
_ => error!(var.lit => "'rename' value must be a string literal"),
}
}
NestedMeta::Meta(Meta::Path(path)) if path.is_ident("flatten") => {
this.flatten = true;
}
_ => continue,
}
}
} }
Ok(this) Ok(this)

View File

@ -1,35 +1,302 @@
use std::convert::TryFrom;
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2::{Ident, Span, TokenStream};
use quote::quote_spanned; use quote::{quote, quote_spanned};
use syn::spanned::Spanned; use syn::spanned::Spanned;
pub(crate) fn updater_type(item: TokenStream) -> Result<TokenStream, syn::Error> { use crate::serde;
use crate::util;
pub(crate) fn updatable(item: TokenStream) -> Result<TokenStream, syn::Error> {
let item: syn::Item = syn::parse2(item)?; let item: syn::Item = syn::parse2(item)?;
let full_span = item.span(); let full_span = item.span();
Ok(match item { match item {
syn::Item::Struct(syn::ItemStruct { syn::Item::Struct(syn::ItemStruct {
ident, generics, .. fields: syn::Fields::Named(named),
}) => derive_updater_type(full_span, ident, generics), attrs,
ident,
generics,
..
}) => derive_named_struct_updatable(attrs, full_span, ident, generics, named),
syn::Item::Struct(syn::ItemStruct {
attrs,
ident,
generics,
..
}) => derive_default_updatable(attrs, full_span, ident, generics),
syn::Item::Enum(syn::ItemEnum { syn::Item::Enum(syn::ItemEnum {
ident, generics, .. attrs,
}) => derive_updater_type(full_span, ident, generics), ident,
_ => bail!(item => "`UpdaterType` cannot be derived for this type"), generics,
}) ..
}) => derive_default_updatable(attrs, full_span, ident, generics),
_ => bail!(item => "`Updatable` can only be derived for structs"),
}
} }
fn no_generics(generics: syn::Generics) { fn no_generics(generics: syn::Generics) {
if let Some(lt) = generics.lt_token { if let Some(lt) = generics.lt_token {
error!(lt => "deriving `UpdaterType` for a generic enum is not supported"); error!(lt => "deriving `Updatable` for a generic enum is not supported");
} else if let Some(wh) = generics.where_clause { } else if let Some(wh) = generics.where_clause {
error!(wh => "deriving `UpdaterType` on enums with generic bounds is not supported"); error!(wh => "deriving `Updatable` on enums with generic bounds is not supported");
} }
} }
fn derive_updater_type(full_span: Span, ident: Ident, generics: syn::Generics) -> TokenStream { fn derive_default_updatable(
attrs: Vec<syn::Attribute>,
full_span: Span,
ident: Ident,
generics: syn::Generics,
) -> Result<TokenStream, syn::Error> {
no_generics(generics); no_generics(generics);
let args = UpdatableArgs::from_attributes(attrs);
if let Some(updater) = args.updater {
error!(updater => "`updater` updater attribute not supported for this type");
}
Ok(default_updatable(full_span, ident))
}
fn default_updatable(full_span: Span, ident: Ident) -> TokenStream {
quote_spanned! { full_span => quote_spanned! { full_span =>
impl ::proxmox_schema::UpdaterType for #ident { #[automatically_derived]
type Updater = Option<Self>; impl ::proxmox::api::schema::Updatable for #ident {
type Updater = Option<#ident>;
const UPDATER_IS_OPTION: bool = true;
fn update_from<T: AsRef<str>>(
&mut self,
from: Option<#ident>,
_delete: &[T],
) -> Result<(), ::anyhow::Error> {
if let Some(val) = from {
*self = val;
}
Ok(())
}
fn try_build_from(from: Option<#ident>) -> Result<Self, ::anyhow::Error> {
from.ok_or_else(|| ::anyhow::format_err!("cannot build from None value"))
}
} }
} }
} }
fn derive_named_struct_updatable(
attrs: Vec<syn::Attribute>,
full_span: Span,
ident: Ident,
generics: syn::Generics,
fields: syn::FieldsNamed,
) -> Result<TokenStream, syn::Error> {
no_generics(generics);
let serde_container_attrs = serde::ContainerAttrib::try_from(&attrs[..])?;
let args = UpdatableArgs::from_attributes(attrs);
let updater = match args.updater {
Some(updater) => updater,
None => return Ok(default_updatable(full_span, ident)),
};
let mut delete = TokenStream::new();
let mut apply = TokenStream::new();
let mut build = TokenStream::new();
for field in fields.named {
let serde_attrs = serde::SerdeAttrib::try_from(&field.attrs[..])?;
let attrs = UpdaterFieldArgs::from_attributes(field.attrs);
let field_ident = field
.ident
.as_ref()
.expect("unnamed field in named struct?");
let field_name_string = if let Some(renamed) = serde_attrs.rename {
renamed.into_str()
} else if let Some(rename_all) = serde_container_attrs.rename_all {
let name = rename_all.apply_to_field(&field_ident.to_string());
name
} else {
field_ident.to_string()
};
let build_err = format!(
"failed to build value for field '{}': {{}}",
field_name_string
);
if util::is_option_type(&field.ty).is_some() {
delete.extend(quote! {
#field_name_string => { self.#field_ident = None; }
});
build.extend(quote! {
#field_ident: ::proxmox::api::schema::Updatable::try_build_from(
from.#field_ident
)
.map_err(|err| ::anyhow::format_err!(#build_err, err))?,
});
} else {
build.extend(quote! {
#field_ident: ::proxmox::api::schema::Updatable::try_build_from(
from.#field_ident
)
.map_err(|err| ::anyhow::format_err!(#build_err, err))?,
});
}
if attrs.fixed {
let error = format!(
"field '{}' must not be set when updating existing data",
field_ident
);
apply.extend(quote! {
if from.#field_ident.is_some() {
::anyhow::bail!(#error);
}
});
} else {
apply.extend(quote! {
::proxmox::api::schema::Updatable::update_from(
&mut self.#field_ident,
from.#field_ident,
delete,
)?;
});
}
}
if !delete.is_empty() {
delete = quote! {
for delete in delete {
match delete.as_ref() {
#delete
_ => continue,
}
}
};
}
Ok(quote! {
#[automatically_derived]
impl ::proxmox::api::schema::Updatable for #ident {
type Updater = #updater;
const UPDATER_IS_OPTION: bool = false;
fn update_from<T: AsRef<str>>(
&mut self,
from: Self::Updater,
delete: &[T],
) -> Result<(), ::anyhow::Error> {
#delete
#apply
Ok(())
}
fn try_build_from(from: Self::Updater) -> Result<Self, ::anyhow::Error> {
Ok(Self {
#build
})
}
}
})
}
#[derive(Default)]
struct UpdatableArgs {
updater: Option<syn::Type>,
}
impl UpdatableArgs {
fn from_attributes(attributes: Vec<syn::Attribute>) -> Self {
let mut this = Self::default();
for_attributes(attributes, "updatable", |meta| this.parse_nested(meta));
this
}
fn parse_nested(&mut self, meta: syn::NestedMeta) -> Result<(), syn::Error> {
match meta {
syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) => self.parse_name_value(nv),
other => bail!(other => "invalid updater argument"),
}
}
fn parse_name_value(&mut self, nv: syn::MetaNameValue) -> Result<(), syn::Error> {
if nv.path.is_ident("updater") {
let updater: syn::Type = match nv.lit {
// we could use `s.parse()` but it doesn't make sense to put the original struct
// name as spanning info here, so instead, we use the call site:
syn::Lit::Str(s) => syn::parse_str(&s.value())?,
other => bail!(other => "updater argument must be a string literal"),
};
if self.updater.is_some() {
error!(updater.span(), "multiple 'updater' attributes not allowed");
}
self.updater = Some(updater);
Ok(())
} else {
bail!(nv.path => "unrecognized updater argument");
}
}
}
#[derive(Default)]
struct UpdaterFieldArgs {
/// A fixed field must not be set in the `Updater` when the data is updated via `update_from`,
/// but is still required for the `build()` method.
fixed: bool,
}
impl UpdaterFieldArgs {
fn from_attributes(attributes: Vec<syn::Attribute>) -> Self {
let mut this = Self::default();
for_attributes(attributes, "updater", |meta| this.parse_nested(meta));
this
}
fn parse_nested(&mut self, meta: syn::NestedMeta) -> Result<(), syn::Error> {
match meta {
syn::NestedMeta::Meta(syn::Meta::Path(path)) if path.is_ident("fixed") => {
self.fixed = true;
}
other => bail!(other => "invalid updater argument"),
}
Ok(())
}
}
/// Non-fatally go through all `updater` attributes.
fn for_attributes<F>(attributes: Vec<syn::Attribute>, attr_name: &str, mut func: F)
where
F: FnMut(syn::NestedMeta) -> Result<(), syn::Error>,
{
for meta in meta_iter(attributes) {
let list = match meta {
syn::Meta::List(list) if list.path.is_ident(attr_name) => list,
_ => continue,
};
for entry in list.nested {
match func(entry) {
Ok(()) => (),
Err(err) => crate::add_error(err),
}
}
}
}
fn meta_iter(
attributes: impl IntoIterator<Item = syn::Attribute>,
) -> impl Iterator<Item = syn::Meta> {
attributes.into_iter().filter_map(|attr| {
if attr.style != syn::AttrStyle::Outer {
return None;
}
attr.parse_meta().ok()
})
}

View File

@ -2,7 +2,7 @@ use std::borrow::Borrow;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use proc_macro2::{Ident, Span, TokenStream};
use quote::ToTokens; use quote::ToTokens;
use syn::parse::{Parse, ParseStream}; use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
@ -376,7 +376,7 @@ impl IntoIterator for JSONObject {
/// An element in a json style map. /// An element in a json style map.
struct JSONMapEntry { struct JSONMapEntry {
pub key: FieldName, pub key: FieldName,
_colon_token: Token![:], pub colon_token: Token![:],
pub value: JSONValue, pub value: JSONValue,
} }
@ -384,7 +384,7 @@ impl Parse for JSONMapEntry {
fn parse(input: ParseStream) -> syn::Result<Self> { fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self { Ok(Self {
key: input.parse()?, key: input.parse()?,
_colon_token: input.parse()?, colon_token: input.parse()?,
value: input.parse()?, value: input.parse()?,
}) })
} }
@ -496,12 +496,7 @@ pub fn infer_type(schema: &mut Schema, ty: &syn::Type) -> Result<bool, syn::Erro
} else if api::NUMBERNAMES.iter().any(|n| path.path.is_ident(n)) { } else if api::NUMBERNAMES.iter().any(|n| path.path.is_ident(n)) {
schema.item = SchemaItem::Number(ty.span()); schema.item = SchemaItem::Number(ty.span());
} else { } else {
// bail!(ty => "cannot infer parameter type from this rust type"); bail!(ty => "cannot infer parameter type from this rust type");
schema.item = SchemaItem::ExternType(syn::ExprPath {
attrs: Vec::new(),
qself: path.qself.clone(),
path: path.path.clone(),
});
} }
} }
_ => (), _ => (),
@ -561,7 +556,7 @@ pub fn make_path(span: Span, leading_colon: bool, path: &[&str]) -> syn::Path {
None None
}, },
segments: path segments: path
.iter() .into_iter()
.map(|entry| syn::PathSegment { .map(|entry| syn::PathSegment {
ident: Ident::new(entry, span), ident: Ident::new(entry, span),
arguments: syn::PathArguments::None, arguments: syn::PathArguments::None,
@ -682,9 +677,9 @@ impl<T> Maybe<T> {
} }
} }
impl<T> From<Maybe<T>> for Option<T> { impl<T> Into<Option<T>> for Maybe<T> {
fn from(maybe: Maybe<T>) -> Option<T> { fn into(self) -> Option<T> {
match maybe { match self {
Maybe::Explicit(t) | Maybe::Derived(t) => Some(t), Maybe::Explicit(t) | Maybe::Derived(t) => Some(t),
Maybe::None => None, Maybe::None => None,
} }
@ -694,16 +689,11 @@ impl<T> From<Maybe<T>> for Option<T> {
/// Helper to iterate over all the `#[derive(...)]` types found in an attribute list. /// Helper to iterate over all the `#[derive(...)]` types found in an attribute list.
pub fn derived_items(attributes: &[syn::Attribute]) -> DerivedItems { pub fn derived_items(attributes: &[syn::Attribute]) -> DerivedItems {
DerivedItems { DerivedItems {
attributes: attributes.iter(), attributes: attributes.into_iter(),
current: None, current: None,
} }
} }
/// Helper to check if a certain trait is being derived.
pub fn derives_trait(attributes: &[syn::Attribute], ident: &str) -> bool {
derived_items(attributes).any(|p| p.is_ident(ident))
}
/// Iterator over the types found in `#[derive(...)]` attributes. /// Iterator over the types found in `#[derive(...)]` attributes.
pub struct DerivedItems<'a> { pub struct DerivedItems<'a> {
current: Option<<Punctuated<syn::NestedMeta, Token![,]> as IntoIterator>::IntoIter>, current: Option<<Punctuated<syn::NestedMeta, Token![,]> as IntoIterator>::IntoIter>,
@ -821,116 +811,3 @@ pub fn make_derive_attribute(span: Span, content: TokenStream) -> syn::Attribute
quote::quote! { (#content) }, quote::quote! { (#content) },
) )
} }
/// Extract (remove) an attribute from a list and run a callback on its parameters.
pub fn extract_attributes(
attributes: &mut Vec<syn::Attribute>,
attr_name: &str,
mut func_matching: impl FnMut(&syn::Attribute, syn::NestedMeta) -> Result<(), syn::Error>,
) {
for attr in std::mem::take(attributes) {
if attr.style != syn::AttrStyle::Outer {
attributes.push(attr);
continue;
}
let meta = match attr.parse_meta() {
Ok(meta) => meta,
Err(err) => {
crate::add_error(err);
attributes.push(attr);
continue;
}
};
let list = match meta {
syn::Meta::List(list) if list.path.is_ident(attr_name) => list,
_ => {
attributes.push(attr);
continue;
}
};
for entry in list.nested {
match func_matching(&attr, entry) {
Ok(()) => (),
Err(err) => crate::add_error(err),
}
}
}
}
/// Helper to create an error about some duplicate attribute.
pub fn duplicate<T>(prev: &Option<T>, attr: &syn::Path) {
if prev.is_some() {
error!(attr => "duplicate attribute: '{:?}'", attr)
}
}
/// Set a boolean attribute to a value, producing a "duplication" error if it has already been set.
pub fn set_bool(b: &mut Option<syn::LitBool>, attr: &syn::Path, value: bool) {
duplicate(&*b, attr);
*b = Some(syn::LitBool::new(value, attr.span()));
}
pub fn default_false(o: Option<&syn::LitBool>) -> bool {
o.as_ref().map(|b| b.value).unwrap_or(false)
}
/// Parse the contents of a `LitStr`, preserving its span.
pub fn parse_lit_str<T: Parse>(s: &syn::LitStr) -> syn::parse::Result<T> {
parse_str(&s.value(), s.span())
}
/// Parse a literal string, giving the entire output the specified span.
pub fn parse_str<T: Parse>(s: &str, span: Span) -> syn::parse::Result<T> {
syn::parse2(respan_tokens(syn::parse_str(s)?, span))
}
/// Apply a `Span` to an entire `TokenStream`.
pub fn respan_tokens(stream: TokenStream, span: Span) -> TokenStream {
stream
.into_iter()
.map(|token| respan(token, span))
.collect()
}
/// Apply a `Span` to a `TokenTree`, recursively if it is a `Group`.
pub fn respan(mut token: TokenTree, span: Span) -> TokenTree {
use proc_macro2::Group;
match &mut token {
TokenTree::Group(g) => {
*g = Group::new(g.delimiter(), respan_tokens(g.stream(), span));
}
other => other.set_span(span),
}
token
}
/// Parse a string attribute into a value, producing a duplication error if it has already been
/// set.
pub fn parse_str_value_to_option<T: Parse>(target: &mut Option<T>, nv: &syn::MetaNameValue) {
duplicate(&*target, &nv.path);
match &nv.lit {
syn::Lit::Str(s) => match parse_lit_str(s) {
Ok(value) => *target = Some(value),
Err(err) => crate::add_error(err),
},
other => error!(other => "bad value for '{:?}' attribute", nv.path),
}
}
/*
pub fn parse_str_value<T: Parse>(nv: &syn::MetaNameValue) -> Result<T, syn::Error> {
match &nv.lit {
syn::Lit::Str(s) => super::parse_lit_str(s),
other => bail!(other => "bad value for '{:?}' attribute", nv.path),
}
}
pub fn default_true(o: Option<&syn::LitBool>) -> bool {
o.as_ref().map(|b| b.value).unwrap_or(true)
}
*/

View File

@ -4,9 +4,8 @@ use anyhow::Error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{json, Value}; use serde_json::{json, Value};
use proxmox::api::schema;
use proxmox_api_macro::api; use proxmox_api_macro::api;
use proxmox_schema as schema;
use proxmox_schema::ApiType;
pub const NAME_SCHEMA: schema::Schema = schema::StringSchema::new("Name.").schema(); pub const NAME_SCHEMA: schema::Schema = schema::StringSchema::new("Name.").schema();
pub const VALUE_SCHEMA: schema::Schema = schema::IntegerSchema::new("Value.").schema(); pub const VALUE_SCHEMA: schema::Schema = schema::IntegerSchema::new("Value.").schema();
@ -57,16 +56,17 @@ pub struct Nvit {
#[test] #[test]
fn test_nvit() { fn test_nvit() {
const TEST_NAME_VALUE_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::ObjectSchema::new( const TEST_NAME_VALUE_SCHEMA: ::proxmox::api::schema::Schema =
"Name and value.", ::proxmox::api::schema::ObjectSchema::new(
&[ "Name and value.",
("name", false, &NAME_SCHEMA), &[
("value", false, &VALUE_SCHEMA), ("name", false, &NAME_SCHEMA),
], ("value", false, &VALUE_SCHEMA),
) ],
.schema(); )
.schema();
const TEST_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::AllOfSchema::new( const TEST_SCHEMA: ::proxmox::api::schema::Schema = ::proxmox::api::schema::AllOfSchema::new(
"Name, value, index and text.", "Name, value, index and text.",
&[&TEST_NAME_VALUE_SCHEMA, &IndexText::API_SCHEMA], &[&TEST_NAME_VALUE_SCHEMA, &IndexText::API_SCHEMA],
) )
@ -96,17 +96,17 @@ struct WithExtra {
#[test] #[test]
fn test_extra() { fn test_extra() {
const INNER_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::ObjectSchema::new( const INNER_SCHEMA: ::proxmox::api::schema::Schema = ::proxmox::api::schema::ObjectSchema::new(
"<INNER: Extra Schema>", "<INNER: Extra Schema>",
&[( &[(
"extra", "extra",
false, false,
&::proxmox_schema::StringSchema::new("Extra field.").schema(), &::proxmox::api::schema::StringSchema::new("Extra field.").schema(),
)], )],
) )
.schema(); .schema();
const TEST_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::AllOfSchema::new( const TEST_SCHEMA: ::proxmox::api::schema::Schema = ::proxmox::api::schema::AllOfSchema::new(
"Extra Schema", "Extra Schema",
&[ &[
&INNER_SCHEMA, &INNER_SCHEMA,
@ -134,9 +134,9 @@ pub fn hello(it: IndexText, nv: NameValue) -> Result<(NameValue, IndexText), Err
#[test] #[test]
fn hello_schema_check() { fn hello_schema_check() {
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new_full( const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new_full(
&::proxmox_router::ApiHandler::Sync(&api_function_hello), &::proxmox::api::ApiHandler::Sync(&api_function_hello),
::proxmox_schema::ParameterSchema::AllOf(&::proxmox_schema::AllOfSchema::new( ::proxmox::api::schema::ParameterSchema::AllOf(&::proxmox::api::schema::AllOfSchema::new(
"Hello method.", "Hello method.",
&[&IndexText::API_SCHEMA, &NameValue::API_SCHEMA], &[&IndexText::API_SCHEMA, &NameValue::API_SCHEMA],
)), )),
@ -164,19 +164,19 @@ pub fn with_extra(
#[test] #[test]
fn with_extra_schema_check() { fn with_extra_schema_check() {
const INNER_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::ObjectSchema::new( const INNER_SCHEMA: ::proxmox::api::schema::Schema = ::proxmox::api::schema::ObjectSchema::new(
"<INNER: Extra method.>", "<INNER: Extra method.>",
&[( &[(
"extra", "extra",
false, false,
&::proxmox_schema::StringSchema::new("An extra field.").schema(), &::proxmox::api::schema::StringSchema::new("An extra field.").schema(),
)], )],
) )
.schema(); .schema();
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new_full( const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new_full(
&::proxmox_router::ApiHandler::Sync(&api_function_with_extra), &::proxmox::api::ApiHandler::Sync(&api_function_with_extra),
::proxmox_schema::ParameterSchema::AllOf(&::proxmox_schema::AllOfSchema::new( ::proxmox::api::schema::ParameterSchema::AllOf(&::proxmox::api::schema::AllOfSchema::new(
"Extra method.", "Extra method.",
&[ &[
&INNER_SCHEMA, &INNER_SCHEMA,
@ -189,7 +189,7 @@ fn with_extra_schema_check() {
} }
struct RpcEnv; struct RpcEnv;
impl proxmox_router::RpcEnvironment for RpcEnv { impl proxmox::api::RpcEnvironment for RpcEnv {
fn result_attrib_mut(&mut self) -> &mut Value { fn result_attrib_mut(&mut self) -> &mut Value {
panic!("result_attrib_mut called"); panic!("result_attrib_mut called");
} }
@ -199,7 +199,7 @@ impl proxmox_router::RpcEnvironment for RpcEnv {
} }
/// The environment type /// The environment type
fn env_type(&self) -> proxmox_router::RpcEnvironmentType { fn env_type(&self) -> proxmox::api::RpcEnvironmentType {
panic!("env_type called"); panic!("env_type called");
} }

View File

@ -3,7 +3,7 @@ use proxmox_api_macro::api;
use anyhow::Error; use anyhow::Error;
use serde_json::{json, Value}; use serde_json::{json, Value};
use proxmox_router::Permission; use proxmox::api::Permission;
#[api( #[api(
input: { input: {
@ -31,7 +31,7 @@ use proxmox_router::Permission;
}, },
"CSRFPreventionToken": { "CSRFPreventionToken": {
type: String, type: String,
description: "Cross Site Request Forgery Prevention Token.", description: "Cross Site Request Forgerty Prevention Token.",
}, },
}, },
}, },
@ -59,49 +59,51 @@ pub fn create_ticket(param: Value) -> Result<Value, Error> {
#[test] #[test]
fn create_ticket_schema_check() { fn create_ticket_schema_check() {
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new( const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox_router::ApiHandler::Sync(&api_function_create_ticket), &::proxmox::api::ApiHandler::Sync(&api_function_create_ticket),
&::proxmox_schema::ObjectSchema::new( &::proxmox::api::schema::ObjectSchema::new(
"Create or verify authentication ticket.", "Create or verify authentication ticket.",
&[ &[
( (
"password", "password",
false, false,
&::proxmox_schema::StringSchema::new("The secret password or a valid ticket.") &::proxmox::api::schema::StringSchema::new(
.schema(), "The secret password or a valid ticket.",
)
.schema(),
), ),
( (
"username", "username",
false, false,
&::proxmox_schema::StringSchema::new("User name") &::proxmox::api::schema::StringSchema::new("User name")
.max_length(64) .max_length(64)
.schema(), .schema(),
), ),
], ],
), ),
) )
.returns(::proxmox_schema::ReturnType::new( .returns(::proxmox::api::router::ReturnType::new(
false, false,
&::proxmox_schema::ObjectSchema::new( &::proxmox::api::schema::ObjectSchema::new(
"A ticket.", "A ticket.",
&[ &[
( (
"CSRFPreventionToken", "CSRFPreventionToken",
false, false,
&::proxmox_schema::StringSchema::new( &::proxmox::api::schema::StringSchema::new(
"Cross Site Request Forgery Prevention Token.", "Cross Site Request Forgerty Prevention Token.",
) )
.schema(), .schema(),
), ),
( (
"ticket", "ticket",
false, false,
&::proxmox_schema::StringSchema::new("Auth ticket.").schema(), &::proxmox::api::schema::StringSchema::new("Auth ticket.").schema(),
), ),
( (
"username", "username",
false, false,
&::proxmox_schema::StringSchema::new("User name.").schema(), &::proxmox::api::schema::StringSchema::new("User name.").schema(),
), ),
], ],
) )
@ -138,7 +140,7 @@ fn create_ticket_schema_check() {
}, },
"CSRFPreventionToken": { "CSRFPreventionToken": {
type: String, type: String,
description: "Cross Site Request Forgery Prevention Token.", description: "Cross Site Request Forgerty Prevention Token.",
}, },
}, },
}, },
@ -160,49 +162,51 @@ pub fn create_ticket_direct(username: String, password: String) -> Result<&'stat
#[test] #[test]
fn create_ticket_direct_schema_check() { fn create_ticket_direct_schema_check() {
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new( const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox_router::ApiHandler::Sync(&api_function_create_ticket_direct), &::proxmox::api::ApiHandler::Sync(&api_function_create_ticket_direct),
&::proxmox_schema::ObjectSchema::new( &::proxmox::api::schema::ObjectSchema::new(
"Create or verify authentication ticket.", "Create or verify authentication ticket.",
&[ &[
( (
"password", "password",
false, false,
&::proxmox_schema::StringSchema::new("The secret password or a valid ticket.") &::proxmox::api::schema::StringSchema::new(
.schema(), "The secret password or a valid ticket.",
)
.schema(),
), ),
( (
"username", "username",
false, false,
&::proxmox_schema::StringSchema::new("User name") &::proxmox::api::schema::StringSchema::new("User name")
.max_length(64) .max_length(64)
.schema(), .schema(),
), ),
], ],
), ),
) )
.returns(::proxmox_schema::ReturnType::new( .returns(::proxmox::api::router::ReturnType::new(
false, false,
&::proxmox_schema::ObjectSchema::new( &::proxmox::api::schema::ObjectSchema::new(
"A ticket.", "A ticket.",
&[ &[
( (
"CSRFPreventionToken", "CSRFPreventionToken",
false, false,
&::proxmox_schema::StringSchema::new( &::proxmox::api::schema::StringSchema::new(
"Cross Site Request Forgery Prevention Token.", "Cross Site Request Forgerty Prevention Token.",
) )
.schema(), .schema(),
), ),
( (
"ticket", "ticket",
false, false,
&::proxmox_schema::StringSchema::new("Auth ticket.").schema(), &::proxmox::api::schema::StringSchema::new("Auth ticket.").schema(),
), ),
( (
"username", "username",
false, false,
&::proxmox_schema::StringSchema::new("User name.").schema(), &::proxmox::api::schema::StringSchema::new("User name.").schema(),
), ),
], ],
) )
@ -235,22 +239,6 @@ pub fn basic_function() -> Result<(), Error> {
Ok(()) Ok(())
} }
#[api(
streaming: true,
)]
/// streaming async call
pub async fn streaming_async_call() -> Result<(), Error> {
Ok(())
}
#[api(
streaming: true,
)]
/// streaming sync call
pub fn streaming_sync_call() -> Result<(), Error> {
Ok(())
}
#[api( #[api(
input: { input: {
properties: { properties: {
@ -270,14 +258,14 @@ pub fn func_with_option(verbose: Option<bool>) -> Result<(), Error> {
#[test] #[test]
fn func_with_option_schema_check() { fn func_with_option_schema_check() {
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new( const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox_router::ApiHandler::Sync(&api_function_func_with_option), &::proxmox::api::ApiHandler::Sync(&api_function_func_with_option),
&::proxmox_schema::ObjectSchema::new( &::proxmox::api::schema::ObjectSchema::new(
"Optional parameter", "Optional parameter",
&[( &[(
"verbose", "verbose",
true, true,
&::proxmox_schema::BooleanSchema::new("Verbose output.").schema(), &::proxmox::api::schema::BooleanSchema::new("Verbose output.").schema(),
)], )],
), ),
) )
@ -287,7 +275,7 @@ fn func_with_option_schema_check() {
} }
struct RpcEnv; struct RpcEnv;
impl proxmox_router::RpcEnvironment for RpcEnv { impl proxmox::api::RpcEnvironment for RpcEnv {
fn result_attrib_mut(&mut self) -> &mut Value { fn result_attrib_mut(&mut self) -> &mut Value {
panic!("result_attrib_mut called"); panic!("result_attrib_mut called");
} }
@ -297,7 +285,7 @@ impl proxmox_router::RpcEnvironment for RpcEnv {
} }
/// The environment type /// The environment type
fn env_type(&self) -> proxmox_router::RpcEnvironmentType { fn env_type(&self) -> proxmox::api::RpcEnvironmentType {
panic!("env_type called"); panic!("env_type called");
} }

View File

@ -34,14 +34,14 @@ pub async fn number(num: u32) -> Result<u32, Error> {
#[test] #[test]
fn number_schema_check() { fn number_schema_check() {
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new( const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox_router::ApiHandler::Async(&api_function_number), &::proxmox::api::ApiHandler::Async(&api_function_number),
&::proxmox_schema::ObjectSchema::new( &::proxmox::api::schema::ObjectSchema::new(
"Return the number...", "Return the number...",
&[( &[(
"num", "num",
false, false,
&::proxmox_schema::IntegerSchema::new("The version to upgrade to") &::proxmox::api::schema::IntegerSchema::new("The version to upgrade to")
.minimum(0) .minimum(0)
.maximum(0xffffffff) .maximum(0xffffffff)
.schema(), .schema(),
@ -75,20 +75,20 @@ pub async fn more_async_params(param: Value) -> Result<(), Error> {
#[test] #[test]
fn more_async_params_schema_check() { fn more_async_params_schema_check() {
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new( const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox_router::ApiHandler::Async(&api_function_more_async_params), &::proxmox::api::ApiHandler::Async(&api_function_more_async_params),
&::proxmox_schema::ObjectSchema::new( &::proxmox::api::schema::ObjectSchema::new(
"Return the number...", "Return the number...",
&[ &[
( (
"bar", "bar",
false, false,
&::proxmox_schema::StringSchema::new("The great Bar").schema(), &::proxmox::api::schema::StringSchema::new("The great Bar").schema(),
), ),
( (
"foo", "foo",
false, false,
&::proxmox_schema::StringSchema::new("The great Foo").schema(), &::proxmox::api::schema::StringSchema::new("The great Foo").schema(),
), ),
], ],
), ),
@ -116,14 +116,14 @@ pub async fn keyword_named_parameters(r#type: String) -> Result<(), Error> {
#[test] #[test]
fn keyword_named_parameters_check() { fn keyword_named_parameters_check() {
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new( const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox_router::ApiHandler::Async(&api_function_keyword_named_parameters), &::proxmox::api::ApiHandler::Async(&api_function_keyword_named_parameters),
&::proxmox_schema::ObjectSchema::new( &::proxmox::api::schema::ObjectSchema::new(
"Returns nothing.", "Returns nothing.",
&[( &[(
"type", "type",
false, false,
&::proxmox_schema::StringSchema::new("The great Foo").schema(), &::proxmox::api::schema::StringSchema::new("The great Foo").schema(),
)], )],
), ),
) )

View File

@ -1,9 +1,8 @@
//! This should test the usage of "external" schemas. If a property is declared with a path instead //! This should test the usage of "external" schemas. If a property is declared with a path instead
//! of an object, we expect the path to lead to a schema. //! of an object, we expect the path to lead to a schema.
use proxmox::api::{schema, RpcEnvironment};
use proxmox_api_macro::api; use proxmox_api_macro::api;
use proxmox_router::RpcEnvironment;
use proxmox_schema as schema;
use anyhow::Error; use anyhow::Error;
use serde_json::{json, Value}; use serde_json::{json, Value};
@ -28,9 +27,9 @@ pub fn get_archive(archive_name: String) {
#[test] #[test]
fn get_archive_schema_check() { fn get_archive_schema_check() {
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new( const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox_router::ApiHandler::Sync(&api_function_get_archive), &::proxmox::api::ApiHandler::Sync(&api_function_get_archive),
&::proxmox_schema::ObjectSchema::new( &::proxmox::api::schema::ObjectSchema::new(
"Get an archive.", "Get an archive.",
&[("archive-name", false, &NAME_SCHEMA)], &[("archive-name", false, &NAME_SCHEMA)],
), ),
@ -57,9 +56,9 @@ pub fn get_archive_2(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Va
#[test] #[test]
fn get_archive_2_schema_check() { fn get_archive_2_schema_check() {
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new( const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox_router::ApiHandler::Sync(&api_function_get_archive_2), &::proxmox::api::ApiHandler::Sync(&api_function_get_archive_2),
&::proxmox_schema::ObjectSchema::new( &::proxmox::api::schema::ObjectSchema::new(
"Get an archive.", "Get an archive.",
&[("archive-name", false, &NAME_SCHEMA)], &[("archive-name", false, &NAME_SCHEMA)],
), ),
@ -89,14 +88,14 @@ pub fn get_data(param: Value) -> Result<(), Error> {
#[test] #[test]
fn get_data_schema_test() { fn get_data_schema_test() {
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new( const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox_router::ApiHandler::Sync(&api_function_get_data), &::proxmox::api::ApiHandler::Sync(&api_function_get_data),
&::proxmox_schema::ObjectSchema::new( &::proxmox::api::schema::ObjectSchema::new(
"Get data.", "Get data.",
&[( &[(
"data", "data",
false, false,
&::proxmox_schema::ArraySchema::new("The data", &NAME_SCHEMA).schema(), &::proxmox::api::schema::ArraySchema::new("The data", &NAME_SCHEMA).schema(),
)], )],
), ),
) )

View File

@ -1,7 +1,6 @@
//! Test the automatic addition of integer limits. //! Test the automatic addition of integer limits.
use proxmox_api_macro::api; use proxmox_api_macro::api;
use proxmox_schema::ApiType;
/// An i16: -32768 to 32767. /// An i16: -32768 to 32767.
#[api] #[api]
@ -9,8 +8,8 @@ pub struct AnI16(i16);
#[test] #[test]
fn test_an_i16_schema() { fn test_an_i16_schema() {
const TEST_SCHEMA: ::proxmox_schema::Schema = const TEST_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox_schema::IntegerSchema::new("An i16: -32768 to 32767.") ::proxmox::api::schema::IntegerSchema::new("An i16: -32768 to 32767.")
.minimum(-32768) .minimum(-32768)
.maximum(32767) .maximum(32767)
.schema(); .schema();
@ -24,8 +23,8 @@ pub struct I16G50(i16);
#[test] #[test]
fn test_i16g50_schema() { fn test_i16g50_schema() {
const TEST_SCHEMA: ::proxmox_schema::Schema = const TEST_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox_schema::IntegerSchema::new("Already limited on one side.") ::proxmox::api::schema::IntegerSchema::new("Already limited on one side.")
.minimum(-50) .minimum(-50)
.maximum(32767) .maximum(32767)
.schema(); .schema();
@ -39,8 +38,8 @@ pub struct AnI32(i32);
#[test] #[test]
fn test_an_i32_schema() { fn test_an_i32_schema() {
const TEST_SCHEMA: ::proxmox_schema::Schema = const TEST_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox_schema::IntegerSchema::new("An i32: -0x8000_0000 to 0x7fff_ffff.") ::proxmox::api::schema::IntegerSchema::new("An i32: -0x8000_0000 to 0x7fff_ffff.")
.minimum(-0x8000_0000) .minimum(-0x8000_0000)
.maximum(0x7fff_ffff) .maximum(0x7fff_ffff)
.schema(); .schema();
@ -54,8 +53,8 @@ pub struct AnU32(u32);
#[test] #[test]
fn test_an_u32_schema() { fn test_an_u32_schema() {
const TEST_SCHEMA: ::proxmox_schema::Schema = const TEST_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox_schema::IntegerSchema::new("Unsigned implies a minimum of zero.") ::proxmox::api::schema::IntegerSchema::new("Unsigned implies a minimum of zero.")
.minimum(0) .minimum(0)
.maximum(0xffff_ffff) .maximum(0xffff_ffff)
.schema(); .schema();
@ -69,8 +68,8 @@ pub struct AnI64(i64);
#[test] #[test]
fn test_an_i64_schema() { fn test_an_i64_schema() {
const TEST_SCHEMA: ::proxmox_schema::Schema = const TEST_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox_schema::IntegerSchema::new("An i64: this is left unlimited.").schema(); ::proxmox::api::schema::IntegerSchema::new("An i64: this is left unlimited.").schema();
assert_eq!(TEST_SCHEMA, AnI64::API_SCHEMA); assert_eq!(TEST_SCHEMA, AnI64::API_SCHEMA);
} }
@ -81,8 +80,8 @@ pub struct AnU64(u64);
#[test] #[test]
fn test_an_u64_schema() { fn test_an_u64_schema() {
const TEST_SCHEMA: ::proxmox_schema::Schema = const TEST_SCHEMA: ::proxmox::api::schema::Schema =
::proxmox_schema::IntegerSchema::new("Unsigned implies a minimum of zero.") ::proxmox::api::schema::IntegerSchema::new("Unsigned implies a minimum of zero.")
.minimum(0) .minimum(0)
.schema(); .schema();

View File

@ -40,7 +40,7 @@ pub fn test_default_macro(value: Option<isize>) -> Result<isize, Error> {
} }
struct RpcEnv; struct RpcEnv;
impl proxmox_router::RpcEnvironment for RpcEnv { impl proxmox::api::RpcEnvironment for RpcEnv {
fn result_attrib_mut(&mut self) -> &mut Value { fn result_attrib_mut(&mut self) -> &mut Value {
panic!("result_attrib_mut called"); panic!("result_attrib_mut called");
} }
@ -50,7 +50,7 @@ impl proxmox_router::RpcEnvironment for RpcEnv {
} }
/// The environment type /// The environment type
fn env_type(&self) -> proxmox_router::RpcEnvironmentType { fn env_type(&self) -> proxmox::api::RpcEnvironmentType {
panic!("env_type called"); panic!("env_type called");
} }

View File

@ -3,9 +3,8 @@
#![allow(dead_code)] #![allow(dead_code)]
use proxmox::api::schema::{self, EnumEntry};
use proxmox_api_macro::api; use proxmox_api_macro::api;
use proxmox_schema as schema;
use proxmox_schema::{ApiType, EnumEntry};
use anyhow::Error; use anyhow::Error;
use serde::Deserialize; use serde::Deserialize;
@ -24,12 +23,13 @@ pub struct OkString(String);
#[test] #[test]
fn ok_string() { fn ok_string() {
const TEST_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::StringSchema::new("A string") const TEST_SCHEMA: ::proxmox::api::schema::Schema =
.format(&schema::ApiStringFormat::Enum(&[ ::proxmox::api::schema::StringSchema::new("A string")
EnumEntry::new("ok", "Ok"), .format(&schema::ApiStringFormat::Enum(&[
EnumEntry::new("not-ok", "Not OK"), EnumEntry::new("ok", "Ok"),
])) EnumEntry::new("not-ok", "Not OK"),
.schema(); ]))
.schema();
assert_eq!(TEST_SCHEMA, OkString::API_SCHEMA); assert_eq!(TEST_SCHEMA, OkString::API_SCHEMA);
} }
@ -45,23 +45,26 @@ pub struct TestStruct {
#[test] #[test]
fn test_struct() { fn test_struct() {
pub const TEST_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::ObjectSchema::new( pub const TEST_SCHEMA: ::proxmox::api::schema::Schema =
"An example of a simple struct type.", ::proxmox::api::schema::ObjectSchema::new(
&[ "An example of a simple struct type.",
( &[
"another", (
true, "another",
&::proxmox_schema::StringSchema::new("An optional auto-derived value for testing:") true,
&::proxmox::api::schema::StringSchema::new(
"An optional auto-derived value for testing:",
)
.schema(), .schema(),
), ),
( (
"test_string", "test_string",
false, false,
&::proxmox_schema::StringSchema::new("A test string.").schema(), &::proxmox::api::schema::StringSchema::new("A test string.").schema(),
), ),
], ],
) )
.schema(); .schema();
assert_eq!(TEST_SCHEMA, TestStruct::API_SCHEMA); assert_eq!(TEST_SCHEMA, TestStruct::API_SCHEMA);
} }
@ -81,19 +84,21 @@ pub struct RenamedStruct {
#[test] #[test]
fn renamed_struct() { fn renamed_struct() {
const TEST_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::ObjectSchema::new( const TEST_SCHEMA: ::proxmox::api::schema::Schema = ::proxmox::api::schema::ObjectSchema::new(
"An example of a struct with renamed fields.", "An example of a struct with renamed fields.",
&[ &[
( (
"SomeOther", "SomeOther",
true, true,
&::proxmox_schema::StringSchema::new("An optional auto-derived value for testing:") &::proxmox::api::schema::StringSchema::new(
.schema(), "An optional auto-derived value for testing:",
)
.schema(),
), ),
( (
"test-string", "test-string",
false, false,
&::proxmox_schema::StringSchema::new("A test string.").schema(), &::proxmox::api::schema::StringSchema::new("A test string.").schema(),
), ),
], ],
) )
@ -103,7 +108,7 @@ fn renamed_struct() {
} }
#[api] #[api]
#[derive(Default, Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
/// A selection of either 'onekind', 'another-kind' or 'selection-number-three'. /// A selection of either 'onekind', 'another-kind' or 'selection-number-three'.
pub enum Selection { pub enum Selection {
@ -111,7 +116,6 @@ pub enum Selection {
#[serde(rename = "onekind")] #[serde(rename = "onekind")]
OneKind, OneKind,
/// Some other kind. /// Some other kind.
#[default]
AnotherKind, AnotherKind,
/// And yet another. /// And yet another.
SelectionNumberThree, SelectionNumberThree,
@ -119,15 +123,14 @@ pub enum Selection {
#[test] #[test]
fn selection_test() { fn selection_test() {
const TEST_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::StringSchema::new( const TEST_SCHEMA: ::proxmox::api::schema::Schema = ::proxmox::api::schema::StringSchema::new(
"A selection of either \'onekind\', \'another-kind\' or \'selection-number-three\'.", "A selection of either \'onekind\', \'another-kind\' or \'selection-number-three\'.",
) )
.format(&::proxmox_schema::ApiStringFormat::Enum(&[ .format(&::proxmox::api::schema::ApiStringFormat::Enum(&[
EnumEntry::new("onekind", "The first kind."), EnumEntry::new("onekind", "The first kind."),
EnumEntry::new("another-kind", "Some other kind."), EnumEntry::new("another-kind", "Some other kind."),
EnumEntry::new("selection-number-three", "And yet another."), EnumEntry::new("selection-number-three", "And yet another."),
])) ]))
.default("another-kind")
.schema(); .schema();
assert_eq!(TEST_SCHEMA, Selection::API_SCHEMA); assert_eq!(TEST_SCHEMA, Selection::API_SCHEMA);
@ -154,9 +157,9 @@ pub fn string_check(arg: Value, selection: Selection) -> Result<bool, Error> {
#[test] #[test]
fn string_check_schema_test() { fn string_check_schema_test() {
const TEST_METHOD: ::proxmox_router::ApiMethod = ::proxmox_router::ApiMethod::new( const TEST_METHOD: ::proxmox::api::ApiMethod = ::proxmox::api::ApiMethod::new(
&::proxmox_router::ApiHandler::Sync(&api_function_string_check), &::proxmox::api::ApiHandler::Sync(&api_function_string_check),
&::proxmox_schema::ObjectSchema::new( &::proxmox::api::schema::ObjectSchema::new(
"Check a string.", "Check a string.",
&[ &[
("arg", false, &OkString::API_SCHEMA), ("arg", false, &OkString::API_SCHEMA),
@ -164,9 +167,9 @@ fn string_check_schema_test() {
], ],
), ),
) )
.returns(::proxmox_schema::ReturnType::new( .returns(::proxmox::api::router::ReturnType::new(
true, true,
&::proxmox_schema::BooleanSchema::new("Whether the string was \"ok\".").schema(), &::proxmox::api::schema::BooleanSchema::new("Whether the string was \"ok\".").schema(),
)) ))
.protected(false); .protected(false);

View File

@ -1,74 +1,32 @@
#![allow(dead_code)] #[cfg(not(feature = "noserde"))]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value;
use proxmox_schema::{api, ApiType, Updater, UpdaterType}; use proxmox::api::api;
use proxmox::api::schema::Updater;
// Helpers for type checks:
struct AssertTypeEq<T>(T);
macro_rules! assert_type_eq {
($what:ident, $a:ty, $b:ty) => {
#[allow(dead_code, unreachable_patterns)]
fn $what(have: AssertTypeEq<$a>) {
match have {
AssertTypeEq::<$b>(_) => (),
}
}
};
}
#[api(min_length: 3, max_length: 64)]
#[derive(UpdaterType)]
/// Custom String.
pub struct Custom(String);
assert_type_eq!(
custom_type,
<Custom as UpdaterType>::Updater,
Option<Custom>
);
#[api] #[api]
/// An example of a simple struct type. /// An example of a simple struct type.
#[derive(Updater)] #[cfg_attr(not(feature = "noserde"), derive(Deserialize, Serialize))]
#[derive(Debug, PartialEq, Updater)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct Simple { pub struct Simple {
/// A test string. /// A test string.
one_field: String, one_field: String,
/// Another test value. /// An optional auto-derived value for testing:
#[serde(skip_serializing_if = "Option::is_empty")] #[serde(skip_serializing_if = "Option::is_empty")]
opt: Option<String>, opt: Option<String>,
} }
#[test]
fn test_simple() {
pub const TEST_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::ObjectSchema::new(
"An example of a simple struct type.",
&[
(
"one-field",
true,
&::proxmox_schema::StringSchema::new("A test string.").schema(),
),
(
"opt",
true,
&::proxmox_schema::StringSchema::new("Another test value.").schema(),
),
],
)
.schema();
assert_eq!(TEST_SCHEMA, SimpleUpdater::API_SCHEMA);
}
#[api( #[api(
properties: { properties: {
simple: { type: Simple }, simple: { type: Simple },
}, },
)] )]
/// A second struct so we can test flattening. /// A second struct so we can test flattening.
#[derive(Updater)] #[cfg_attr(not(feature = "noserde"), derive(Deserialize, Serialize))]
#[derive(Debug, PartialEq, Updater)]
pub struct Complex { pub struct Complex {
/// An extra field not part of the flattened struct. /// An extra field not part of the flattened struct.
extra: String, extra: String,
@ -86,72 +44,288 @@ pub struct Complex {
}, },
)] )]
/// One of the baaaad cases. /// One of the baaaad cases.
#[derive(Updater)] #[cfg_attr(not(feature = "noserde"), derive(Deserialize, Serialize))]
#[serde(rename_all = "kebab-case")] #[derive(Debug, PartialEq, Updater)]
pub struct SuperComplex { pub struct SuperComplex {
/// An extra field. /// An extra field not part of the flattened struct.
extra: String, extra: String,
simple: Simple, #[serde(skip_serializing_if = "Updater::is_empty")]
simple: Option<Simple>,
/// A field which should not appear in the updater.
#[updater(skip)]
not_in_updater: String,
/// A custom type with an Updatable implementation.
custom: Custom,
}
#[test]
fn test_super_complex() {
pub const TEST_SCHEMA: ::proxmox_schema::Schema = ::proxmox_schema::ObjectSchema::new(
"One of the baaaad cases.",
&[
("custom", true, &<Option<Custom> as ApiType>::API_SCHEMA),
(
"extra",
true,
&::proxmox_schema::StringSchema::new("An extra field.").schema(),
),
(
"simple",
true,
//&<<Simple as UpdaterType>::Updater as ApiType>::API_SCHEMA,
&SimpleUpdater::API_SCHEMA,
),
],
)
.schema();
assert_eq!(TEST_SCHEMA, SuperComplexUpdater::API_SCHEMA);
}
#[api]
/// A simple string wrapper.
#[derive(Default, Deserialize, Serialize, Updater)]
struct MyType(String);
impl proxmox_schema::UpdaterType for MyType {
type Updater = Option<Self>;
}
impl MyType {
fn should_skip(&self) -> bool {
self.0 == "skipme"
}
} }
#[api( #[api(
properties: { properties: {
more: { type: MyType }, complex: { type: Complex },
}, },
)] )]
/// A struct where we replace serde attributes. /// Something with "fixed" values we cannot update but require for creation.
#[derive(Deserialize, Serialize, Updater)] #[cfg_attr(not(feature = "noserde"), derive(Deserialize, Serialize))]
pub struct WithSerde { #[derive(Debug, PartialEq, Updater)]
/// Simple string. pub struct Creatable {
data: String, /// An ID which cannot be changed later.
#[updater(fixed)]
id: String,
#[serde(skip_serializing_if = "MyType::should_skip", default)] /// Some parameter we're allowed to change with an updater.
#[updater(serde(skip_serializing_if = "Option::is_none"))] name: String,
more: MyType,
/// Optional additional information.
#[serde(skip_serializing_if = "Updater::is_empty", default)]
info: Option<String>,
/// Optional additional information 2.
#[serde(skip_serializing_if = "Updater::is_empty", default)]
info2: Option<String>,
/// Super complex additional data
#[serde(flatten)]
complex: Complex,
}
struct RpcEnv;
impl proxmox::api::RpcEnvironment for RpcEnv {
fn result_attrib_mut(&mut self) -> &mut Value {
panic!("result_attrib_mut called");
}
fn result_attrib(&self) -> &Value {
panic!("result_attrib called");
}
/// The environment type
fn env_type(&self) -> proxmox::api::RpcEnvironmentType {
panic!("env_type called");
}
/// Set authentication id
fn set_auth_id(&mut self, user: Option<String>) {
let _ = user;
panic!("set_auth_id called");
}
/// Get authentication id
fn get_auth_id(&self) -> Option<String> {
panic!("get_auth_id called");
}
}
mod test_creatable {
use anyhow::{bail, Error};
use serde_json::json;
use proxmox::api::schema::Updatable;
use proxmox_api_macro::api;
use super::*;
static mut TEST_OBJECT: Option<Creatable> = None;
#[api(
input: {
properties: {
thing: { flatten: true, type: CreatableUpdater },
},
},
)]
/// Test method to create an object.
///
/// Returns: the object's ID.
pub fn create_thing(thing: CreatableUpdater) -> Result<String, Error> {
if unsafe { TEST_OBJECT.is_some() } {
bail!("object exists");
}
let obj = Creatable::try_build_from(thing)?;
let id = obj.id.clone();
unsafe {
TEST_OBJECT = Some(obj);
}
Ok(id)
}
#[api(
input: {
properties: {
thing: { flatten: true, type: CreatableUpdater },
delete: {
optional: true,
description: "list of properties to delete",
type: Array,
items: {
description: "field name to delete",
type: String,
},
},
},
},
)]
/// Test method to update an object.
pub fn update_thing(thing: CreatableUpdater, delete: Option<Vec<String>>) -> Result<(), Error> {
let delete = delete.unwrap_or_default();
match unsafe { &mut TEST_OBJECT } {
Some(obj) => obj.update_from(thing, &delete),
None => bail!("object has not been created yet"),
}
}
#[test]
fn test() {
let _ = api_function_create_thing(
json!({ "name": "The Name" }),
&API_METHOD_CREATE_THING,
&mut RpcEnv,
)
.expect_err("create_thing should fail without an ID");
let _ = api_function_create_thing(
json!({ "id": "Id1" }),
&API_METHOD_CREATE_THING,
&mut RpcEnv,
)
.expect_err("create_thing should fail without a name");
let value = api_function_create_thing(
json!({
"id": "Id1",
"name": "The Name",
"extra": "Extra Info",
"one-field": "Part of Simple",
"info2": "More Info 2",
}),
&API_METHOD_CREATE_THING,
&mut RpcEnv,
)
.expect("create_thing should work");
assert_eq!(value, "Id1");
assert_eq!(
unsafe { &TEST_OBJECT },
&Some(Creatable {
id: "Id1".to_string(),
name: "The Name".to_string(),
info: None,
info2: Some("More Info 2".to_string()),
complex: Complex {
extra: "Extra Info".to_string(),
simple: Simple {
one_field: "Part of Simple".to_string(),
opt: None,
},
},
}),
);
let _ = api_function_update_thing(
json!({
"id": "Poop",
}),
&API_METHOD_UPDATE_THING,
&mut RpcEnv,
)
.expect_err("shouldn't be allowed to update the ID");
let _ = api_function_update_thing(
json!({
"info": "Updated Info",
"delete": ["info2"],
}),
&API_METHOD_UPDATE_THING,
&mut RpcEnv,
)
.expect("should be allowed to update the optional field");
assert_eq!(
unsafe { &TEST_OBJECT },
&Some(Creatable {
id: "Id1".to_string(),
name: "The Name".to_string(),
info: Some("Updated Info".to_string()),
info2: None,
complex: Complex {
extra: "Extra Info".to_string(),
simple: Simple {
one_field: "Part of Simple".to_string(),
opt: None,
},
},
}),
);
let _ = api_function_update_thing(
json!({
"extra": "Partial flatten update",
}),
&API_METHOD_UPDATE_THING,
&mut RpcEnv,
)
.expect("should be allowed to update the parts of a flattened field");
assert_eq!(
unsafe { &TEST_OBJECT },
&Some(Creatable {
id: "Id1".to_string(),
name: "The Name".to_string(),
info: Some("Updated Info".to_string()),
info2: None,
complex: Complex {
extra: "Partial flatten update".to_string(),
simple: Simple {
one_field: "Part of Simple".to_string(),
opt: None,
},
},
}),
);
let _ = api_function_update_thing(
json!({
"opt": "Deeply nested optional update.",
}),
&API_METHOD_UPDATE_THING,
&mut RpcEnv,
)
.expect("should be allowed to update the parts of a deeply nested struct");
assert_eq!(
unsafe { &TEST_OBJECT },
&Some(Creatable {
id: "Id1".to_string(),
name: "The Name".to_string(),
info: Some("Updated Info".to_string()),
info2: None,
complex: Complex {
extra: "Partial flatten update".to_string(),
simple: Simple {
one_field: "Part of Simple".to_string(),
opt: Some("Deeply nested optional update.".to_string()),
},
},
}),
);
let _ = api_function_update_thing(
json!({
"delete": ["opt"],
}),
&API_METHOD_UPDATE_THING,
&mut RpcEnv,
)
.expect("should be allowed to remove parts of a deeply nested struct");
assert_eq!(
unsafe { &TEST_OBJECT },
&Some(Creatable {
id: "Id1".to_string(),
name: "The Name".to_string(),
info: Some("Updated Info".to_string()),
info2: None,
complex: Complex {
extra: "Partial flatten update".to_string(),
simple: Simple {
one_field: "Part of Simple".to_string(),
opt: None,
},
},
}),
);
}
} }

View File

@ -1,23 +0,0 @@
[package]
name = "proxmox-apt"
version = "0.9.4"
description = "Proxmox library for APT"
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
exclude = [ "debian" ]
[dependencies]
anyhow.workspace = true
hex.workspace = true
once_cell.workspace = true
openssl.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
rfc822-like = "0.2.1"
proxmox-schema = { workspace = true, features = [ "api-macro" ] }

View File

@ -1,121 +0,0 @@
rust-proxmox-apt (0.9.4-1) bullseye; urgency=medium
* repositories: also detect repository with next suite as configure
-- Proxmox Support Team <support@proxmox.com> Fri, 09 Jun 2023 10:47:41 +0200
rust-proxmox-apt (0.9.3-1) stable; urgency=medium
* packages file: add section field
* deb822: source index support
-- Proxmox Support Team <support@proxmox.com> Wed, 19 Oct 2022 16:17:11 +0200
rust-proxmox-apt (0.9.2-1) stable; urgency=medium
* release: add Commands file reference type
* release: add 'architecture' helper
* release: fix typo in 'Acquire-By-Hash'
-- Proxmox Support Team <support@proxmox.com> Fri, 16 Sep 2022 14:17:10 +0200
rust-proxmox-apt (0.9.1-1) stable; urgency=medium
* release-file: improve invalid file-reference handling
* add ceph quincy repositories
-- Proxmox Support Team <support@proxmox.com> Tue, 6 Sep 2022 10:33:17 +0200
rust-proxmox-apt (0.9.0-1) stable; urgency=medium
* AptRepositoryFile: make path optional
-- Proxmox Support Team <support@proxmox.com> Thu, 21 Jul 2022 13:25:20 +0200
rust-proxmox-apt (0.8.1-1) stable; urgency=medium
* upgrade to 2021 edition
* check suites: add special check for Debian security repository
* file: add pre-parsed content variant
* add module for parsing Packages and Release (deb822 like) files
-- Proxmox Support Team <support@proxmox.com> Thu, 21 Jul 2022 12:08:23 +0200
rust-proxmox-apt (0.8.0-1) stable; urgency=medium
* update to proxox-schema crate
-- Proxmox Support Team <support@proxmox.com> Fri, 08 Oct 2021 11:55:47 +0200
rust-proxmox-apt (0.7.0-1) stable; urgency=medium
* update to proxmox 0.13.0
-- Proxmox Support Team <support@proxmox.com> Tue, 24 Aug 2021 15:38:52 +0200
rust-proxmox-apt (0.6.0-1) stable; urgency=medium
* standard repos: add suite parameter for stricter detection
* check repos: have caller specify the current suite
* add type DebianCodename
-- Proxmox Support Team <support@proxmox.com> Thu, 29 Jul 2021 18:06:54 +0200
rust-proxmox-apt (0.5.1-1) stable; urgency=medium
* depend on proxmox 0.12.0
-- Proxmox Support Team <support@proxmox.com> Tue, 20 Jul 2021 13:18:02 +0200
rust-proxmox-apt (0.5.0-1) stable; urgency=medium
* standard repo detection: handle alternative URI for PVE repos
-- Proxmox Support Team <support@proxmox.com> Fri, 16 Jul 2021 16:19:06 +0200
rust-proxmox-apt (0.4.0-1) stable; urgency=medium
* support quote-word parsing for one-line format
* avoid backtick unicode symbol in string
-- Proxmox Support Team <support@proxmox.com> Thu, 01 Jul 2021 18:33:12 +0200
rust-proxmox-apt (0.3.1-1) stable; urgency=medium
* standard repos: allow conversion from handle and improve information
-- Proxmox Support Team <support@proxmox.com> Wed, 30 Jun 2021 20:42:52 +0200
rust-proxmox-apt (0.3.0-1) stable; urgency=medium
* add get_cached_origin method and an initial config module
* check: return 'origin' property instead of 'badge' for official host
* standard repos: drop product acronym from repo name
-- Proxmox Support Team <support@proxmox.com> Wed, 30 Jun 2021 13:29:13 +0200
rust-proxmox-apt (0.2.0-1) stable; urgency=medium
* Add functions to check repositories.
* Add handling of standard Proxmox repositories.
-- Proxmox Support Team <support@proxmox.com> Wed, 23 Jun 2021 14:57:52 +0200
rust-proxmox-apt (0.1.0-1) stable; urgency=medium
* Initial release.
-- Proxmox Support Team <support@proxmox.com> Thu, 18 Feb 2021 10:20:44 +0100

View File

@ -1,52 +0,0 @@
Source: rust-proxmox-apt
Section: rust
Priority: optional
Build-Depends: debhelper (>= 12),
dh-cargo (>= 25),
cargo:native <!nocheck>,
rustc:native <!nocheck>,
libstd-rust-dev <!nocheck>,
librust-anyhow-1+default-dev <!nocheck>,
librust-hex-0.4+default-dev <!nocheck>,
librust-once-cell-1+default-dev (>= 1.3.1-~~) <!nocheck>,
librust-openssl-0.10+default-dev <!nocheck>,
librust-proxmox-schema-1+api-macro-dev (>= 1.3.7-~~) <!nocheck>,
librust-proxmox-schema-1+default-dev (>= 1.3.7-~~) <!nocheck>,
librust-rfc822-like-0.2+default-dev (>= 0.2.1-~~) <!nocheck>,
librust-serde-1+default-dev <!nocheck>,
librust-serde-1+derive-dev <!nocheck>,
librust-serde-json-1+default-dev <!nocheck>
Maintainer: Proxmox Support Team <support@proxmox.com>
Standards-Version: 4.6.1
Vcs-Git: git://git.proxmox.com/git/proxmox-apt.git
Vcs-Browser: https://git.proxmox.com/?p=proxmox-apt.git
Homepage: https://www.proxmox.com
X-Cargo-Crate: proxmox-apt
Rules-Requires-Root: no
Package: librust-proxmox-apt-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-anyhow-1+default-dev,
librust-hex-0.4+default-dev,
librust-once-cell-1+default-dev (>= 1.3.1-~~),
librust-openssl-0.10+default-dev,
librust-proxmox-schema-1+api-macro-dev (>= 1.3.7-~~),
librust-proxmox-schema-1+default-dev (>= 1.3.7-~~),
librust-rfc822-like-0.2+default-dev (>= 0.2.1-~~),
librust-serde-1+default-dev,
librust-serde-1+derive-dev,
librust-serde-json-1+default-dev
Provides:
librust-proxmox-apt+default-dev (= ${binary:Version}),
librust-proxmox-apt-0-dev (= ${binary:Version}),
librust-proxmox-apt-0+default-dev (= ${binary:Version}),
librust-proxmox-apt-0.9-dev (= ${binary:Version}),
librust-proxmox-apt-0.9+default-dev (= ${binary:Version}),
librust-proxmox-apt-0.9.3-dev (= ${binary:Version}),
librust-proxmox-apt-0.9.3+default-dev (= ${binary:Version})
Description: Proxmox library for APT - Rust source code
This package contains the source for the Rust proxmox-apt crate, packaged by
debcargo for use with cargo and dh-cargo.

View File

@ -1,18 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Files:
*
Copyright: 2019 - 2023 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/>.

View File

@ -1,7 +0,0 @@
overlay = "."
crate_src_path = ".."
maintainer = "Proxmox Support Team <support@proxmox.com>"
[source]
vcs_git = "git://git.proxmox.com/git/proxmox-apt.git"
vcs_browser = "https://git.proxmox.com/?p=proxmox-apt.git"

View File

@ -1,35 +0,0 @@
use once_cell::sync::OnceCell;
static GLOBAL_CONFIG: OnceCell<APTConfig> = OnceCell::new();
/// APT configuration variables.
pub struct APTConfig {
/// Dir::State
pub dir_state: String,
/// Dir::State::Lists
pub dir_state_lists: String,
}
impl APTConfig {
/// Create a new configuration overriding the provided values.
pub fn new(dir_state: Option<&str>, dir_state_lists: Option<&str>) -> Self {
Self {
dir_state: dir_state.unwrap_or("/var/lib/apt/").to_string(),
dir_state_lists: dir_state_lists.unwrap_or("lists/").to_string(),
}
}
}
/// Get the configuration.
///
/// Initializes with default values if init() wasn't called before.
pub fn get() -> &'static APTConfig {
GLOBAL_CONFIG.get_or_init(|| APTConfig::new(None, None))
}
/// Initialize the configuration.
///
/// Only has an effect if no init() or get() has been called yet.
pub fn init(config: APTConfig) -> &'static APTConfig {
GLOBAL_CONFIG.get_or_init(|| config)
}

View File

@ -1,75 +0,0 @@
mod release_file;
use anyhow::{bail, Error};
pub use release_file::{CompressionType, FileReference, FileReferenceType, ReleaseFile};
mod packages_file;
pub use packages_file::PackagesFile;
mod sources_file;
pub use sources_file::SourcesFile;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct CheckSums {
pub md5: Option<[u8; 16]>,
pub sha1: Option<[u8; 20]>,
pub sha256: Option<[u8; 32]>,
pub sha512: Option<[u8; 64]>,
}
macro_rules! assert_hash_equality {
($l:expr, $r:expr) => {{
if $l != $r {
bail!("hash mismatch: {} != {}", hex::encode($l), hex::encode($r));
}
}};
}
impl CheckSums {
pub fn is_secure(&self) -> bool {
self.sha256.is_some() || self.sha512.is_some()
}
pub fn verify(&self, input: &[u8]) -> Result<(), Error> {
if !self.is_secure() {
bail!("No SHA256/SHA512 checksum specified.");
}
if let Some(expected) = self.sha512 {
let digest = openssl::sha::sha512(input);
assert_hash_equality!(digest, expected);
Ok(())
} else if let Some(expected) = self.sha256 {
let digest = openssl::sha::sha256(input);
assert_hash_equality!(digest, expected);
Ok(())
} else {
bail!("No trusted checksum found - verification not possible.");
}
}
/// Merge two instances of `CheckSums`.
pub fn merge(&mut self, rhs: &CheckSums) -> Result<(), Error> {
match (self.sha512, rhs.sha512) {
(_, None) => {}
(None, Some(sha512)) => self.sha512 = Some(sha512),
(Some(left), Some(right)) => assert_hash_equality!(left, right),
};
match (self.sha256, rhs.sha256) {
(_, None) => {}
(None, Some(sha256)) => self.sha256 = Some(sha256),
(Some(left), Some(right)) => assert_hash_equality!(left, right),
};
match (self.sha1, rhs.sha1) {
(_, None) => {}
(None, Some(sha1)) => self.sha1 = Some(sha1),
(Some(left), Some(right)) => assert_hash_equality!(left, right),
};
match (self.md5, rhs.md5) {
(_, None) => {}
(None, Some(md5)) => self.md5 = Some(md5),
(Some(left), Some(right)) => assert_hash_equality!(left, right),
};
Ok(())
}
}

View File

@ -1,171 +0,0 @@
use std::collections::HashMap;
use anyhow::{bail, Error};
use rfc822_like::de::Deserializer;
use serde::Deserialize;
use serde_json::Value;
use super::CheckSums;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct PackagesFileRaw {
pub package: String,
pub source: Option<String>,
pub version: String,
pub section: String,
pub priority: String,
pub architecture: String,
pub essential: Option<String>,
pub depends: Option<String>,
pub recommends: Option<String>,
pub suggests: Option<String>,
pub breaks: Option<String>,
pub conflicts: Option<String>,
#[serde(rename = "Installed-Size")]
pub installed_size: Option<String>,
pub maintainer: String,
pub description: String,
pub filename: String,
pub size: String,
#[serde(rename = "Multi-Arch")]
pub multi_arch: Option<String>,
#[serde(rename = "MD5sum")]
pub md5_sum: Option<String>,
#[serde(rename = "SHA1")]
pub sha1: Option<String>,
#[serde(rename = "SHA256")]
pub sha256: Option<String>,
#[serde(rename = "SHA512")]
pub sha512: Option<String>,
#[serde(rename = "Description-md5")]
pub description_md5: Option<String>,
#[serde(flatten)]
pub extra_fields: HashMap<String, Value>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct PackageEntry {
pub package: String,
pub source: Option<String>,
pub version: String,
pub architecture: String,
pub file: String,
pub size: usize,
pub installed_size: Option<usize>,
pub checksums: CheckSums,
pub section: String,
}
#[derive(Debug, Default, PartialEq, Eq)]
/// A parsed representation of a Release file
pub struct PackagesFile {
pub files: Vec<PackageEntry>,
}
impl TryFrom<PackagesFileRaw> for PackageEntry {
type Error = Error;
fn try_from(value: PackagesFileRaw) -> Result<Self, Self::Error> {
let installed_size = match value.installed_size {
Some(val) => Some(val.parse::<usize>()?),
None => None,
};
let mut parsed = PackageEntry {
package: value.package,
source: value.source,
version: value.version,
architecture: value.architecture,
file: value.filename,
size: value.size.parse::<usize>()?,
installed_size,
checksums: CheckSums::default(),
section: value.section,
};
if let Some(md5) = value.md5_sum {
let mut bytes = [0u8; 16];
hex::decode_to_slice(md5, &mut bytes)?;
parsed.checksums.md5 = Some(bytes);
};
if let Some(sha1) = value.sha1 {
let mut bytes = [0u8; 20];
hex::decode_to_slice(sha1, &mut bytes)?;
parsed.checksums.sha1 = Some(bytes);
};
if let Some(sha256) = value.sha256 {
let mut bytes = [0u8; 32];
hex::decode_to_slice(sha256, &mut bytes)?;
parsed.checksums.sha256 = Some(bytes);
};
if let Some(sha512) = value.sha512 {
let mut bytes = [0u8; 64];
hex::decode_to_slice(sha512, &mut bytes)?;
parsed.checksums.sha512 = Some(bytes);
};
if !parsed.checksums.is_secure() {
bail!(
"no strong checksum found for package entry '{}'",
parsed.package
);
}
Ok(parsed)
}
}
impl TryFrom<String> for PackagesFile {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.as_bytes().try_into()
}
}
impl TryFrom<&[u8]> for PackagesFile {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let deserialized = <Vec<PackagesFileRaw>>::deserialize(Deserializer::new(value))?;
deserialized.try_into()
}
}
impl TryFrom<Vec<PackagesFileRaw>> for PackagesFile {
type Error = Error;
fn try_from(value: Vec<PackagesFileRaw>) -> Result<Self, Self::Error> {
let mut files = Vec::with_capacity(value.len());
for entry in value {
let entry: PackageEntry = entry.try_into()?;
files.push(entry);
}
Ok(Self { files })
}
}
#[test]
pub fn test_deb_packages_file() {
let input = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/deb822/packages/deb.debian.org_debian_dists_bullseye_main_binary-amd64_Packages"
));
let deserialized =
<Vec<PackagesFileRaw>>::deserialize(Deserializer::new(input.as_bytes())).unwrap();
//println!("{:?}", deserialized);
let parsed: PackagesFile = deserialized.try_into().unwrap();
//println!("{:?}", parsed);
assert_eq!(parsed.files.len(), 58618);
}

View File

@ -1,618 +0,0 @@
use std::collections::HashMap;
use anyhow::{bail, format_err, Error};
use rfc822_like::de::Deserializer;
use serde::Deserialize;
use serde_json::Value;
use super::CheckSums;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ReleaseFileRaw {
pub architectures: Option<String>,
pub changelogs: Option<String>,
pub codename: Option<String>,
pub components: Option<String>,
pub date: Option<String>,
pub description: Option<String>,
pub label: Option<String>,
pub origin: Option<String>,
pub suite: Option<String>,
pub version: Option<String>,
#[serde(rename = "MD5Sum")]
pub md5_sum: Option<String>,
#[serde(rename = "SHA1")]
pub sha1: Option<String>,
#[serde(rename = "SHA256")]
pub sha256: Option<String>,
#[serde(rename = "SHA512")]
pub sha512: Option<String>,
#[serde(flatten)]
pub extra_fields: HashMap<String, Value>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum CompressionType {
Bzip2,
Gzip,
Lzma,
Xz,
}
pub type Architecture = String;
pub type Component = String;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
/// Type of file reference extraced from path.
///
/// `Packages` and `Sources` will contain further reference to binary or source package files.
/// These are handled in `PackagesFile` and `SourcesFile` respectively.
pub enum FileReferenceType {
/// A `Commands` index listing command to package mappings
Commands(Architecture, Option<CompressionType>),
/// A `Contents` index listing contents of binary packages
Contents(Architecture, Option<CompressionType>),
/// A `Contents` index listing contents of binary udeb packages
ContentsUdeb(Architecture, Option<CompressionType>),
/// A DEP11 `Components` metadata file or `icons` archive
Dep11(Option<CompressionType>),
/// Referenced files which are not really part of the APT repository but only signed for trust-anchor reasons
Ignored,
/// PDiff indices
PDiff,
/// A `Packages` index listing binary package metadata and references
Packages(Architecture, Option<CompressionType>),
/// A compat `Release` file with no relevant content
PseudoRelease(Option<Architecture>),
/// A `Sources` index listing source package metadata and references
Sources(Option<CompressionType>),
/// A `Translation` file
Translation(Option<CompressionType>),
/// Unknown file reference
Unknown,
}
impl FileReferenceType {
fn match_compression(value: &str) -> Result<Option<CompressionType>, Error> {
if value.is_empty() {
return Ok(None);
}
let value = if let Some(stripped) = value.strip_prefix('.') {
stripped
} else {
value
};
match value {
"bz2" => Ok(Some(CompressionType::Bzip2)),
"gz" => Ok(Some(CompressionType::Gzip)),
"lzma" => Ok(Some(CompressionType::Lzma)),
"xz" => Ok(Some(CompressionType::Xz)),
other => bail!("Unexpected file extension '{other}'."),
}
}
pub fn parse(component: &str, path: &str) -> Result<FileReferenceType, Error> {
// everything referenced in a release file should be component-specific
let rest = path
.strip_prefix(&format!("{component}/"))
.ok_or_else(|| format_err!("Doesn't start with component '{component}'"))?;
let parse_binary_dir =
|file_name: &str, arch: &str| parse_binary_dir(file_name, arch, path);
if let Some((dir, rest)) = rest.split_once('/') {
// reference into another subdir
match dir {
"source" => {
// Sources or compat-Release
if let Some((dir, _rest)) = rest.split_once('/') {
if dir == "Sources.diff" {
Ok(FileReferenceType::PDiff)
} else {
Ok(FileReferenceType::Unknown)
}
} else if rest == "Release" {
Ok(FileReferenceType::PseudoRelease(None))
} else if let Some(ext) = rest.strip_prefix("Sources") {
let comp = FileReferenceType::match_compression(ext)?;
Ok(FileReferenceType::Sources(comp))
} else {
Ok(FileReferenceType::Unknown)
}
}
"cnf" => {
if let Some(rest) = rest.strip_prefix("Commands-") {
if let Some((arch, ext)) = rest.rsplit_once('.') {
Ok(FileReferenceType::Commands(
arch.to_owned(),
FileReferenceType::match_compression(ext).ok().flatten(),
))
} else {
Ok(FileReferenceType::Commands(rest.to_owned(), None))
}
} else {
Ok(FileReferenceType::Unknown)
}
}
"dep11" => {
if let Some((_path, ext)) = rest.rsplit_once('.') {
Ok(FileReferenceType::Dep11(
FileReferenceType::match_compression(ext).ok().flatten(),
))
} else {
Ok(FileReferenceType::Dep11(None))
}
}
"debian-installer" => {
// another layer, then like regular repo but pointing at udebs
if let Some((dir, rest)) = rest.split_once('/') {
if let Some(arch) = dir.strip_prefix("binary-") {
// Packages or compat-Release
return parse_binary_dir(rest, arch);
}
}
// all the rest
Ok(FileReferenceType::Unknown)
}
"i18n" => {
if let Some((dir, _rest)) = rest.split_once('/') {
if dir.starts_with("Translation") && dir.ends_with(".diff") {
Ok(FileReferenceType::PDiff)
} else {
Ok(FileReferenceType::Unknown)
}
} else if let Some((_, ext)) = rest.split_once('.') {
Ok(FileReferenceType::Translation(
FileReferenceType::match_compression(ext)?,
))
} else {
Ok(FileReferenceType::Translation(None))
}
}
_ => {
if let Some(arch) = dir.strip_prefix("binary-") {
// Packages or compat-Release
parse_binary_dir(rest, arch)
} else if let Some(_arch) = dir.strip_prefix("installer-") {
// netboot installer checksum files
Ok(FileReferenceType::Ignored)
} else {
// all the rest
Ok(FileReferenceType::Unknown)
}
}
}
} else if let Some(rest) = rest.strip_prefix("Contents-") {
// reference to a top-level file - Contents-*
let (rest, udeb) = if let Some(rest) = rest.strip_prefix("udeb-") {
(rest, true)
} else {
(rest, false)
};
let (arch, comp) = match rest.split_once('.') {
Some((arch, comp_str)) => (
arch.to_owned(),
FileReferenceType::match_compression(comp_str)?,
),
None => (rest.to_owned(), None),
};
if udeb {
Ok(FileReferenceType::ContentsUdeb(arch, comp))
} else {
Ok(FileReferenceType::Contents(arch, comp))
}
} else {
Ok(FileReferenceType::Unknown)
}
}
pub fn compression(&self) -> Option<CompressionType> {
match *self {
FileReferenceType::Commands(_, comp)
| FileReferenceType::Contents(_, comp)
| FileReferenceType::ContentsUdeb(_, comp)
| FileReferenceType::Packages(_, comp)
| FileReferenceType::Sources(comp)
| FileReferenceType::Translation(comp)
| FileReferenceType::Dep11(comp) => comp,
FileReferenceType::Unknown
| FileReferenceType::PDiff
| FileReferenceType::PseudoRelease(_)
| FileReferenceType::Ignored => None,
}
}
pub fn architecture(&self) -> Option<&Architecture> {
match self {
FileReferenceType::Commands(arch, _)
| FileReferenceType::Contents(arch, _)
| FileReferenceType::ContentsUdeb(arch, _)
| FileReferenceType::Packages(arch, _) => Some(arch),
FileReferenceType::PseudoRelease(arch) => arch.as_ref(),
FileReferenceType::Unknown
| FileReferenceType::PDiff
| FileReferenceType::Sources(_)
| FileReferenceType::Dep11(_)
| FileReferenceType::Translation(_)
| FileReferenceType::Ignored => None,
}
}
pub fn is_package_index(&self) -> bool {
matches!(self, FileReferenceType::Packages(_, _) | FileReferenceType::Sources(_))
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct FileReference {
pub path: String,
pub size: usize,
pub checksums: CheckSums,
pub component: Component,
pub file_type: FileReferenceType,
}
impl FileReference {
pub fn basename(&self) -> Result<String, Error> {
match self.file_type.compression() {
Some(_) => {
let (base, _ext) = self
.path
.rsplit_once('.')
.ok_or_else(|| format_err!("compressed file without file extension"))?;
Ok(base.to_owned())
}
None => Ok(self.path.clone()),
}
}
}
#[derive(Debug, Default, PartialEq, Eq)]
/// A parsed representation of a Release file
pub struct ReleaseFile {
/// List of architectures, e.g., `amd64` or `all`.
pub architectures: Vec<String>,
// TODO No-Support-for-Architecture-all
/// URL for changelog queries via `apt changelog`.
pub changelogs: Option<String>,
/// Release codename - single word, e.g., `bullseye`.
pub codename: Option<String>,
/// List of repository areas, e.g., `main`.
pub components: Vec<String>,
/// UTC timestamp of release file generation
pub date: Option<u64>,
/// UTC timestamp of release file expiration
pub valid_until: Option<u64>,
/// Repository description -
// TODO exact format?
pub description: Option<String>,
/// Repository label - single line
pub label: Option<String>,
/// Repository origin - single line
pub origin: Option<String>,
/// Release suite - single word, e.g., `stable`.
pub suite: Option<String>,
/// Release version
pub version: Option<String>,
/// Whether by-hash retrieval of referenced files is possible
pub aquire_by_hash: bool,
/// Files referenced by this `Release` file, e.g., packages indices.
///
/// Grouped by basename, since only the compressed version needs to actually exist on the repository server.
pub files: HashMap<String, Vec<FileReference>>,
}
impl TryFrom<ReleaseFileRaw> for ReleaseFile {
type Error = Error;
fn try_from(value: ReleaseFileRaw) -> Result<Self, Self::Error> {
let mut parsed = ReleaseFile {
architectures: whitespace_split_to_vec(
&value
.architectures
.ok_or_else(|| format_err!("'Architectures' field missing."))?,
),
components: whitespace_split_to_vec(
&value
.components
.ok_or_else(|| format_err!("'Components' field missing."))?,
),
changelogs: value.changelogs,
codename: value.codename,
date: value.date.as_deref().map(parse_date),
valid_until: value
.extra_fields
.get("Valid-Until")
.map(|val| parse_date(&val.to_string())),
description: value.description,
label: value.label,
origin: value.origin,
suite: value.suite,
files: HashMap::new(),
aquire_by_hash: false,
version: value.version,
};
if let Some(val) = value.extra_fields.get("Acquire-By-Hash") {
parsed.aquire_by_hash = *val == "yes";
}
// Fixup bullseye-security release files which have invalid components
if parsed.label.as_deref() == Some("Debian-Security")
&& parsed.codename.as_deref() == Some("bullseye-security")
{
parsed.components = parsed
.components
.into_iter()
.map(|comp| {
if let Some(stripped) = comp.strip_prefix("updates/") {
stripped.to_owned()
} else {
comp
}
})
.collect();
}
let mut references_map: HashMap<String, HashMap<String, FileReference>> = HashMap::new();
if let Some(md5) = value.md5_sum {
for line in md5.lines() {
let (mut file_ref, checksum) =
parse_file_reference(line, 16, parsed.components.as_ref())?;
let checksum = checksum
.try_into()
.map_err(|_err| format_err!("unexpected checksum length"))?;
file_ref.checksums.md5 = Some(checksum);
merge_references(&mut references_map, file_ref)?;
}
}
if let Some(sha1) = value.sha1 {
for line in sha1.lines() {
let (mut file_ref, checksum) =
parse_file_reference(line, 20, parsed.components.as_ref())?;
let checksum = checksum
.try_into()
.map_err(|_err| format_err!("unexpected checksum length"))?;
file_ref.checksums.sha1 = Some(checksum);
merge_references(&mut references_map, file_ref)?;
}
}
if let Some(sha256) = value.sha256 {
for line in sha256.lines() {
let (mut file_ref, checksum) =
parse_file_reference(line, 32, parsed.components.as_ref())?;
let checksum = checksum
.try_into()
.map_err(|_err| format_err!("unexpected checksum length"))?;
file_ref.checksums.sha256 = Some(checksum);
merge_references(&mut references_map, file_ref)?;
}
}
if let Some(sha512) = value.sha512 {
for line in sha512.lines() {
let (mut file_ref, checksum) =
parse_file_reference(line, 64, parsed.components.as_ref())?;
let checksum = checksum
.try_into()
.map_err(|_err| format_err!("unexpected checksum length"))?;
file_ref.checksums.sha512 = Some(checksum);
merge_references(&mut references_map, file_ref)?;
}
}
parsed.files =
references_map
.into_iter()
.fold(parsed.files, |mut map, (base, inner_map)| {
map.insert(base, inner_map.into_values().collect());
map
});
if let Some(insecure) = parsed
.files
.values()
.flatten()
.find(|file| !file.checksums.is_secure())
{
bail!(
"found file reference without strong checksum: {}",
insecure.path
);
}
Ok(parsed)
}
}
impl TryFrom<String> for ReleaseFile {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.as_bytes().try_into()
}
}
impl TryFrom<&[u8]> for ReleaseFile {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let deserialized = ReleaseFileRaw::deserialize(Deserializer::new(value))?;
deserialized.try_into()
}
}
fn whitespace_split_to_vec(list_str: &str) -> Vec<String> {
list_str
.split_ascii_whitespace()
.map(|arch| arch.to_owned())
.collect()
}
fn parse_file_reference(
line: &str,
csum_len: usize,
components: &[String],
) -> Result<(FileReference, Vec<u8>), Error> {
let mut split = line.split_ascii_whitespace();
let checksum = split
.next()
.ok_or_else(|| format_err!("No 'checksum' field in the file reference line."))?;
if checksum.len() > csum_len * 2 {
bail!(
"invalid checksum length: '{}', expected {} bytes",
checksum,
csum_len
);
}
let checksum = hex::decode(checksum)?;
let size = split
.next()
.ok_or_else(|| format_err!("No 'size' field in file reference line."))?
.parse::<usize>()?;
let file = split
.next()
.ok_or_else(|| format_err!("No 'path' field in file reference line."))?
.to_string();
let (component, file_type) = components
.iter()
.find_map(|component| {
if !file.starts_with(&format!("{component}/")) {
return None;
}
Some(
FileReferenceType::parse(component, &file)
.map(|file_type| (component.clone(), file_type)),
)
})
.unwrap_or_else(|| Ok(("UNKNOWN".to_string(), FileReferenceType::Unknown)))?;
Ok((
FileReference {
path: file,
size,
checksums: CheckSums::default(),
component,
file_type,
},
checksum,
))
}
fn parse_date(_date_str: &str) -> u64 {
// TODO implement
0
}
fn parse_binary_dir(file_name: &str, arch: &str, path: &str) -> Result<FileReferenceType, Error> {
if let Some((dir, _rest)) = file_name.split_once('/') {
if dir == "Packages.diff" {
// TODO re-evaluate?
Ok(FileReferenceType::PDiff)
} else {
Ok(FileReferenceType::Unknown)
}
} else if file_name == "Release" {
Ok(FileReferenceType::PseudoRelease(Some(arch.to_owned())))
} else {
let comp = match file_name.strip_prefix("Packages") {
None => {
bail!("found unexpected non-Packages reference to '{path}'")
}
Some(ext) => FileReferenceType::match_compression(ext)?,
};
//println!("compression: {comp:?}");
Ok(FileReferenceType::Packages(arch.to_owned(), comp))
}
}
fn merge_references(
base_map: &mut HashMap<String, HashMap<String, FileReference>>,
file_ref: FileReference,
) -> Result<(), Error> {
let base = file_ref.basename()?;
match base_map.get_mut(&base) {
None => {
let mut map = HashMap::new();
map.insert(file_ref.path.clone(), file_ref);
base_map.insert(base, map);
}
Some(entries) => {
match entries.get_mut(&file_ref.path) {
Some(entry) => {
if entry.size != file_ref.size {
bail!(
"Multiple entries for '{}' with size mismatch: {} / {}",
entry.path,
file_ref.size,
entry.size
);
}
entry.checksums.merge(&file_ref.checksums).map_err(|err| {
format_err!("Multiple checksums for '{}' - {err}", entry.path)
})?;
}
None => {
entries.insert(file_ref.path.clone(), file_ref);
}
};
}
};
Ok(())
}
#[test]
pub fn test_deb_release_file() {
let input = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/deb822/release/deb.debian.org_debian_dists_bullseye_Release"
));
let deserialized = ReleaseFileRaw::deserialize(Deserializer::new(input.as_bytes())).unwrap();
//println!("{:?}", deserialized);
let parsed: ReleaseFile = deserialized.try_into().unwrap();
//println!("{:?}", parsed);
assert_eq!(parsed.files.len(), 315);
}
#[test]
pub fn test_deb_release_file_insecure() {
let input = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/deb822/release/deb.debian.org_debian_dists_bullseye_Release_insecure"
));
let deserialized = ReleaseFileRaw::deserialize(Deserializer::new(input.as_bytes())).unwrap();
//println!("{:?}", deserialized);
let parsed: Result<ReleaseFile, Error> = deserialized.try_into();
assert!(parsed.is_err());
println!("{:?}", parsed);
}

View File

@ -1,257 +0,0 @@
use std::collections::HashMap;
use anyhow::{bail, Error, format_err};
use rfc822_like::de::Deserializer;
use serde::Deserialize;
use serde_json::Value;
use super::CheckSums;
//Uploaders
//
//Homepage
//
//Version Control System (VCS) fields
//
//Testsuite
//
//Dgit
//
//Standards-Version (mandatory)
//
//Build-Depends et al
//
//Package-List (recommended)
//
//Checksums-Sha1 and Checksums-Sha256 (mandatory)
//
//Files (mandatory)
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SourcesFileRaw {
pub format: String,
pub package: String,
pub binary: Option<Vec<String>>,
pub version: String,
pub section: Option<String>,
pub priority: Option<String>,
pub maintainer: String,
pub uploaders: Option<String>,
pub architecture: Option<String>,
pub directory: String,
pub files: String,
#[serde(rename = "Checksums-Sha256")]
pub sha256: Option<String>,
#[serde(rename = "Checksums-Sha512")]
pub sha512: Option<String>,
#[serde(flatten)]
pub extra_fields: HashMap<String, Value>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct SourcePackageEntry {
pub format: String,
pub package: String,
pub binary: Option<Vec<String>>,
pub version: String,
pub architecture: Option<String>,
pub section: Option<String>,
pub priority: Option<String>,
pub maintainer: String,
pub uploaders: Option<String>,
pub directory: String,
pub files: HashMap<String, SourcePackageFileReference>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct SourcePackageFileReference {
pub file: String,
pub size: usize,
pub checksums: CheckSums,
}
impl SourcePackageEntry {
pub fn size(&self) -> usize {
self.files.values().map(|f| f.size).sum()
}
}
#[derive(Debug, Default, PartialEq, Eq)]
/// A parsed representation of a Release file
pub struct SourcesFile {
pub source_packages: Vec<SourcePackageEntry>,
}
impl TryFrom<SourcesFileRaw> for SourcePackageEntry {
type Error = Error;
fn try_from(value: SourcesFileRaw) -> Result<Self, Self::Error> {
let mut parsed = SourcePackageEntry {
package: value.package,
binary: value.binary,
version: value.version,
architecture: value.architecture,
files: HashMap::new(),
format: value.format,
section: value.section,
priority: value.priority,
maintainer: value.maintainer,
uploaders: value.uploaders,
directory: value.directory,
};
for file_reference in value.files.lines() {
let (file_name, size, md5) = parse_file_reference(file_reference, 16)?;
let entry = parsed.files.entry(file_name.clone()).or_insert_with(|| SourcePackageFileReference { file: file_name, size, checksums: CheckSums::default()});
entry.checksums.md5 = Some(md5.try_into().map_err(|_|format_err!("unexpected checksum length"))?);
if entry.size != size {
bail!("Size mismatch: {} != {}", entry.size, size);
}
}
if let Some(sha256) = value.sha256 {
for line in sha256.lines() {
let (file_name, size, sha256) = parse_file_reference(line, 32)?;
let entry = parsed.files.entry(file_name.clone()).or_insert_with(|| SourcePackageFileReference { file: file_name, size, checksums: CheckSums::default()});
entry.checksums.sha256 = Some(sha256.try_into().map_err(|_|format_err!("unexpected checksum length"))?);
if entry.size != size {
bail!("Size mismatch: {} != {}", entry.size, size);
}
}
};
if let Some(sha512) = value.sha512 {
for line in sha512.lines() {
let (file_name, size, sha512) = parse_file_reference(line, 64)?;
let entry = parsed.files.entry(file_name.clone()).or_insert_with(|| SourcePackageFileReference { file: file_name, size, checksums: CheckSums::default()});
entry.checksums.sha512 = Some(sha512.try_into().map_err(|_|format_err!("unexpected checksum length"))?);
if entry.size != size {
bail!("Size mismatch: {} != {}", entry.size, size);
}
}
};
for (file_name, reference) in &parsed.files {
if !reference.checksums.is_secure() {
bail!(
"no strong checksum found for source entry '{}'",
file_name
);
}
}
Ok(parsed)
}
}
impl TryFrom<String> for SourcesFile {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.as_bytes().try_into()
}
}
impl TryFrom<&[u8]> for SourcesFile {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let deserialized = <Vec<SourcesFileRaw>>::deserialize(Deserializer::new(value))?;
deserialized.try_into()
}
}
impl TryFrom<Vec<SourcesFileRaw>> for SourcesFile {
type Error = Error;
fn try_from(value: Vec<SourcesFileRaw>) -> Result<Self, Self::Error> {
let mut source_packages = Vec::with_capacity(value.len());
for entry in value {
let entry: SourcePackageEntry = entry.try_into()?;
source_packages.push(entry);
}
Ok(Self { source_packages })
}
}
fn parse_file_reference(
line: &str,
csum_len: usize,
) -> Result<(String, usize, Vec<u8>), Error> {
let mut split = line.split_ascii_whitespace();
let checksum = split
.next()
.ok_or_else(|| format_err!("Missing 'checksum' field."))?;
if checksum.len() > csum_len * 2 {
bail!(
"invalid checksum length: '{}', expected {} bytes",
checksum,
csum_len
);
}
let checksum = hex::decode(checksum)?;
let size = split
.next()
.ok_or_else(|| format_err!("Missing 'size' field."))?
.parse::<usize>()?;
let file = split
.next()
.ok_or_else(|| format_err!("Missing 'file name' field."))?
.to_string();
Ok((file, size, checksum))
}
#[test]
pub fn test_deb_packages_file() {
// NOTE: test is over an excerpt from packages starting with 0-9, a, b & z using:
// http://snapshot.debian.org/archive/debian/20221017T212657Z/dists/bullseye/main/source/Sources.xz
let input = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/deb822/sources/deb.debian.org_debian_dists_bullseye_main_source_Sources"
));
let deserialized =
<Vec<SourcesFileRaw>>::deserialize(Deserializer::new(input.as_bytes())).unwrap();
assert_eq!(deserialized.len(), 1558);
let parsed: SourcesFile = deserialized.try_into().unwrap();
assert_eq!(parsed.source_packages.len(), 1558);
let found = parsed.source_packages.iter().find(|source| source.package == "base-files").expect("test file contains 'base-files' entry");
assert_eq!(found.package, "base-files");
assert_eq!(found.format, "3.0 (native)");
assert_eq!(found.architecture.as_deref(), Some("any"));
assert_eq!(found.directory, "pool/main/b/base-files");
assert_eq!(found.section.as_deref(), Some("admin"));
assert_eq!(found.version, "11.1+deb11u5");
let binary_packages = found.binary.as_ref().expect("base-files source package builds base-files binary package");
assert_eq!(binary_packages.len(), 1);
assert_eq!(binary_packages[0], "base-files");
let references = &found.files;
assert_eq!(references.len(), 2);
let dsc_file = "base-files_11.1+deb11u5.dsc";
let dsc = references.get(dsc_file).expect("base-files source package contains 'dsc' reference");
assert_eq!(dsc.file, dsc_file);
assert_eq!(dsc.size, 1110);
assert_eq!(dsc.checksums.md5.expect("dsc has md5 checksum"), hex::decode("741c34ac0151262a03de8d5a07bc4271").unwrap()[..]);
assert_eq!(dsc.checksums.sha256.expect("dsc has sha256 checksum"), hex::decode("c41a7f00d57759f27e6068240d1ea7ad80a9a752e4fb43850f7e86e967422bd3").unwrap()[..]);
let tar_file = "base-files_11.1+deb11u5.tar.xz";
let tar = references.get(tar_file).expect("base-files source package contains 'tar' reference");
assert_eq!(tar.file, tar_file);
assert_eq!(tar.size, 65612);
assert_eq!(tar.checksums.md5.expect("tar has md5 checksum"), hex::decode("995df33642118b566a4026410e1c6aac").unwrap()[..]);
assert_eq!(tar.checksums.sha256.expect("tar has sha256 checksum"), hex::decode("31c9e5745845a73f3d5c8a7868c379d77aaca42b81194679d7ab40cc28e3a0e9").unwrap()[..]);
}

View File

@ -1,3 +0,0 @@
pub mod config;
pub mod deb822;
pub mod repositories;

View File

@ -1,469 +0,0 @@
use std::fmt::Display;
use std::path::{Path, PathBuf};
use anyhow::{format_err, Error};
use serde::{Deserialize, Serialize};
use crate::repositories::release::DebianCodename;
use crate::repositories::repository::{
APTRepository, APTRepositoryFileType, APTRepositoryPackageType,
};
use proxmox_schema::api;
mod list_parser;
use list_parser::APTListFileParser;
mod sources_parser;
use sources_parser::APTSourcesFileParser;
trait APTRepositoryParser {
/// Parse all repositories including the disabled ones and push them onto
/// the provided vector.
fn parse_repositories(&mut self) -> Result<Vec<APTRepository>, Error>;
}
#[api(
properties: {
"file-type": {
type: APTRepositoryFileType,
},
repositories: {
description: "List of APT repositories.",
type: Array,
items: {
type: APTRepository,
},
},
digest: {
description: "Digest for the content of the file.",
optional: true,
type: Array,
items: {
description: "Digest byte.",
type: Integer,
},
},
},
)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Represents an abstract APT repository file.
pub struct APTRepositoryFile {
/// The path to the file. If None, `contents` must be set directly.
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
/// The type of the file.
pub file_type: APTRepositoryFileType,
/// List of repositories in the file.
pub repositories: Vec<APTRepository>,
/// The file content, if already parsed.
pub content: Option<String>,
/// Digest of the original contents.
pub digest: Option<[u8; 32]>,
}
#[api]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Error type for problems with APT repository files.
pub struct APTRepositoryFileError {
/// The path to the problematic file.
pub path: String,
/// The error message.
pub error: String,
}
impl Display for APTRepositoryFileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "proxmox-apt error for '{}' - {}", self.path, self.error)
}
}
impl std::error::Error for APTRepositoryFileError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
#[api]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Additional information for a repository.
pub struct APTRepositoryInfo {
/// Path to the defining file.
#[serde(skip_serializing_if = "String::is_empty")]
pub path: String,
/// Index of the associated respository within the file (starting from 0).
pub index: usize,
/// The property from which the info originates (e.g. "Suites")
#[serde(skip_serializing_if = "Option::is_none")]
pub property: Option<String>,
/// Info kind (e.g. "warning")
pub kind: String,
/// Info message
pub message: String,
}
impl APTRepositoryFile {
/// Creates a new `APTRepositoryFile` without parsing.
///
/// If the file is hidden, the path points to a directory, or the extension
/// is usually ignored by APT (e.g. `.orig`), `Ok(None)` is returned, while
/// invalid file names yield an error.
pub fn new<P: AsRef<Path>>(path: P) -> Result<Option<Self>, APTRepositoryFileError> {
let path: PathBuf = path.as_ref().to_path_buf();
let new_err = |path_string: String, err: &str| APTRepositoryFileError {
path: path_string,
error: err.to_string(),
};
let path_string = path
.clone()
.into_os_string()
.into_string()
.map_err(|os_string| {
new_err(
os_string.to_string_lossy().to_string(),
"path is not valid unicode",
)
})?;
let new_err = |err| new_err(path_string.clone(), err);
if path.is_dir() {
return Ok(None);
}
let file_name = match path.file_name() {
Some(file_name) => file_name
.to_os_string()
.into_string()
.map_err(|_| new_err("invalid path"))?,
None => return Err(new_err("invalid path")),
};
if file_name.starts_with('.') || file_name.ends_with('~') {
return Ok(None);
}
let extension = match path.extension() {
Some(extension) => extension
.to_os_string()
.into_string()
.map_err(|_| new_err("invalid path"))?,
None => return Err(new_err("invalid extension")),
};
// See APT's apt-pkg/init.cc
if extension.starts_with("dpkg-")
|| extension.starts_with("ucf-")
|| matches!(
extension.as_str(),
"disabled" | "bak" | "save" | "orig" | "distUpgrade"
)
{
return Ok(None);
}
let file_type = APTRepositoryFileType::try_from(&extension[..])
.map_err(|_| new_err("invalid extension"))?;
if !file_name
.chars()
.all(|x| x.is_ascii_alphanumeric() || x == '_' || x == '-' || x == '.')
{
return Err(new_err("invalid characters in file name"));
}
Ok(Some(Self {
path: Some(path_string),
file_type,
repositories: vec![],
digest: None,
content: None,
}))
}
pub fn with_content(content: String, content_type: APTRepositoryFileType) -> Self {
Self {
file_type: content_type,
content: Some(content),
path: None,
repositories: vec![],
digest: None,
}
}
/// Check if the file exists.
pub fn exists(&self) -> bool {
if let Some(path) = &self.path {
PathBuf::from(path).exists()
} else {
false
}
}
pub fn read_with_digest(&self) -> Result<(Vec<u8>, [u8; 32]), APTRepositoryFileError> {
if let Some(path) = &self.path {
let content = std::fs::read(path).map_err(|err| self.err(format_err!("{}", err)))?;
let digest = openssl::sha::sha256(&content);
Ok((content, digest))
} else if let Some(ref content) = self.content {
let content = content.as_bytes();
let digest = openssl::sha::sha256(content);
Ok((content.to_vec(), digest))
} else {
Err(self.err(format_err!(
"Neither 'path' nor 'content' set, cannot read APT repository info."
)))
}
}
/// Create an `APTRepositoryFileError`.
pub fn err(&self, error: Error) -> APTRepositoryFileError {
APTRepositoryFileError {
path: self.path.clone().unwrap_or_default(),
error: error.to_string(),
}
}
/// Parses the APT repositories configured in the file on disk, including
/// disabled ones.
///
/// Resets the current repositories and digest, even on failure.
pub fn parse(&mut self) -> Result<(), APTRepositoryFileError> {
self.repositories.clear();
self.digest = None;
let (content, digest) = self.read_with_digest()?;
let mut parser: Box<dyn APTRepositoryParser> = match self.file_type {
APTRepositoryFileType::List => Box::new(APTListFileParser::new(&content[..])),
APTRepositoryFileType::Sources => Box::new(APTSourcesFileParser::new(&content[..])),
};
let repos = parser.parse_repositories().map_err(|err| self.err(err))?;
for (n, repo) in repos.iter().enumerate() {
repo.basic_check()
.map_err(|err| self.err(format_err!("check for repository {} - {}", n + 1, err)))?;
}
self.repositories = repos;
self.digest = Some(digest);
Ok(())
}
/// Writes the repositories to the file on disk.
///
/// If a digest is provided, checks that the current content of the file still
/// produces the same one.
pub fn write(&self) -> Result<(), APTRepositoryFileError> {
let path = match &self.path {
Some(path) => path,
None => {
return Err(self.err(format_err!(
"Cannot write to APT repository file without path."
)));
}
};
if let Some(digest) = self.digest {
if !self.exists() {
return Err(self.err(format_err!("digest specified, but file does not exist")));
}
let (_, current_digest) = self.read_with_digest()?;
if digest != current_digest {
return Err(self.err(format_err!("digest mismatch")));
}
}
if self.repositories.is_empty() {
return std::fs::remove_file(&path)
.map_err(|err| self.err(format_err!("unable to remove file - {}", err)));
}
let mut content = vec![];
for (n, repo) in self.repositories.iter().enumerate() {
repo.basic_check()
.map_err(|err| self.err(format_err!("check for repository {} - {}", n + 1, err)))?;
repo.write(&mut content)
.map_err(|err| self.err(format_err!("writing repository {} - {}", n + 1, err)))?;
}
let path = PathBuf::from(&path);
let dir = match path.parent() {
Some(dir) => dir,
None => return Err(self.err(format_err!("invalid path"))),
};
std::fs::create_dir_all(dir)
.map_err(|err| self.err(format_err!("unable to create parent dir - {}", err)))?;
let pid = std::process::id();
let mut tmp_path = path.clone();
tmp_path.set_extension("tmp");
tmp_path.set_extension(format!("{}", pid));
if let Err(err) = std::fs::write(&tmp_path, content) {
let _ = std::fs::remove_file(&tmp_path);
return Err(self.err(format_err!("writing {:?} failed - {}", path, err)));
}
if let Err(err) = std::fs::rename(&tmp_path, &path) {
let _ = std::fs::remove_file(&tmp_path);
return Err(self.err(format_err!("rename failed for {:?} - {}", path, err)));
}
Ok(())
}
/// Checks if old or unstable suites are configured and that the Debian security repository
/// has the correct suite. Also checks that the `stable` keyword is not used.
pub fn check_suites(&self, current_codename: DebianCodename) -> Vec<APTRepositoryInfo> {
let mut infos = vec![];
let path = match &self.path {
Some(path) => path.clone(),
None => return vec![],
};
for (n, repo) in self.repositories.iter().enumerate() {
if !repo.types.contains(&APTRepositoryPackageType::Deb) {
continue;
}
let is_security_repo = repo.uris.iter().any(|uri| {
let uri = uri.trim_end_matches('/');
let uri = uri.strip_suffix("debian-security").unwrap_or(uri);
let uri = uri.trim_end_matches('/');
matches!(
uri,
"http://security.debian.org" | "https://security.debian.org",
)
});
let require_suffix = match is_security_repo {
true if current_codename >= DebianCodename::Bullseye => Some("-security"),
true => Some("/updates"),
false => None,
};
let mut add_info = |kind: &str, message| {
infos.push(APTRepositoryInfo {
path: path.clone(),
index: n,
property: Some("Suites".to_string()),
kind: kind.to_string(),
message,
})
};
let message_old = |suite| format!("old suite '{}' configured!", suite);
let message_new =
|suite| format!("suite '{}' should not be used in production!", suite);
let message_stable = "use the name of the stable distribution instead of 'stable'!";
for suite in repo.suites.iter() {
let (base_suite, suffix) = suite_variant(suite);
match base_suite {
"oldoldstable" | "oldstable" => {
add_info("warning", message_old(base_suite));
}
"testing" | "unstable" | "experimental" | "sid" => {
add_info("warning", message_new(base_suite));
}
"stable" => {
add_info("warning", message_stable.to_string());
}
_ => (),
};
let codename: DebianCodename = match base_suite.try_into() {
Ok(codename) => codename,
Err(_) => continue,
};
if codename < current_codename {
add_info("warning", message_old(base_suite));
}
if Some(codename) == current_codename.next() {
add_info("ignore-pre-upgrade-warning", message_new(base_suite));
} else if codename > current_codename {
add_info("warning", message_new(base_suite));
}
if let Some(require_suffix) = require_suffix {
if suffix != require_suffix {
add_info(
"warning",
format!("expected suite '{}{}'", current_codename, require_suffix),
);
}
}
}
}
infos
}
/// Checks for official URIs.
pub fn check_uris(&self) -> Vec<APTRepositoryInfo> {
let mut infos = vec![];
let path = match &self.path {
Some(path) => path,
None => return vec![],
};
for (n, repo) in self.repositories.iter().enumerate() {
let mut origin = match repo.get_cached_origin() {
Ok(option) => option,
Err(_) => None,
};
if origin.is_none() {
origin = repo.origin_from_uris();
}
if let Some(origin) = origin {
infos.push(APTRepositoryInfo {
path: path.clone(),
index: n,
kind: "origin".to_string(),
property: None,
message: origin,
});
}
}
infos
}
}
/// Splits the suite into its base part and variant.
/// Does not expect the base part to contain either `-` or `/`.
fn suite_variant(suite: &str) -> (&str, &str) {
match suite.find(&['-', '/'][..]) {
Some(n) => (&suite[0..n], &suite[n..]),
None => (suite, ""),
}
}

View File

@ -1,250 +0,0 @@
use std::io::BufRead;
use std::iter::Iterator;
use anyhow::{bail, format_err, Error};
use crate::repositories::{APTRepository, APTRepositoryFileType, APTRepositoryOption};
use super::APTRepositoryParser;
// TODO convert %-escape characters. Also adapt printing back accordingly,
// because at least '%' needs to be re-escaped when printing.
/// See APT's ParseQuoteWord in contrib/strutl.cc
///
/// Doesn't split on whitespace when between `[]` or `""` and strips `"` from the word.
///
/// Currently, %-escaped characters are not interpreted, but passed along as is.
struct SplitQuoteWord {
rest: String,
position: usize,
}
impl SplitQuoteWord {
pub fn new(string: String) -> Self {
Self {
rest: string,
position: 0,
}
}
}
impl Iterator for SplitQuoteWord {
type Item = Result<String, Error>;
fn next(&mut self) -> Option<Self::Item> {
let rest = &self.rest[self.position..];
let mut start = None;
let mut wait_for = None;
for (n, c) in rest.chars().enumerate() {
self.position += 1;
if let Some(wait_for_char) = wait_for {
if wait_for_char == c {
wait_for = None;
}
continue;
}
if char::is_ascii_whitespace(&c) {
if let Some(start) = start {
return Some(Ok(rest[start..n].replace('"', "")));
}
continue;
}
if start == None {
start = Some(n);
}
if c == '"' {
wait_for = Some('"');
}
if c == '[' {
wait_for = Some(']');
}
}
if let Some(wait_for) = wait_for {
return Some(Err(format_err!("missing terminating '{}'", wait_for)));
}
if let Some(start) = start {
return Some(Ok(rest[start..].replace('"', "")));
}
None
}
}
pub struct APTListFileParser<R: BufRead> {
input: R,
line_nr: usize,
comment: String,
}
impl<R: BufRead> APTListFileParser<R> {
pub fn new(reader: R) -> Self {
Self {
input: reader,
line_nr: 0,
comment: String::new(),
}
}
/// Helper to parse options from the existing token stream.
///
/// Also returns `Ok(())` if there are no options.
///
/// Errors when options are invalid or not closed by `']'`.
fn parse_options(
options: &mut Vec<APTRepositoryOption>,
tokens: &mut SplitQuoteWord,
) -> Result<(), Error> {
let mut finished = false;
loop {
let mut option = match tokens.next() {
Some(token) => token?,
None => bail!("options not closed by ']'"),
};
if let Some(stripped) = option.strip_suffix(']') {
option = stripped.to_string();
if option.is_empty() {
break;
}
finished = true; // but still need to handle the last one
};
if let Some(mid) = option.find('=') {
let (key, mut value_str) = option.split_at(mid);
value_str = &value_str[1..];
if key.is_empty() {
bail!("option has no key: '{}'", option);
}
if value_str.is_empty() {
bail!("option has no value: '{}'", option);
}
let values: Vec<String> = value_str
.split(',')
.map(|value| value.to_string())
.collect();
options.push(APTRepositoryOption {
key: key.to_string(),
values,
});
} else if !option.is_empty() {
bail!("got invalid option - '{}'", option);
}
if finished {
break;
}
}
Ok(())
}
/// Parse a repository or comment in one-line format.
///
/// Commented out repositories are also detected and returned with the
/// `enabled` property set to `false`.
///
/// If the line contains a repository, `self.comment` is added to the
/// `comment` property.
///
/// If the line contains a comment, it is added to `self.comment`.
fn parse_one_line(&mut self, mut line: &str) -> Result<Option<APTRepository>, Error> {
line = line.trim_matches(|c| char::is_ascii_whitespace(&c));
// check for commented out repository first
if let Some(commented_out) = line.strip_prefix('#') {
if let Ok(Some(mut repo)) = self.parse_one_line(commented_out) {
repo.set_enabled(false);
return Ok(Some(repo));
}
}
let mut repo = APTRepository::new(APTRepositoryFileType::List);
// now handle "real" comment
if let Some(comment_start) = line.find('#') {
let (line_start, comment) = line.split_at(comment_start);
self.comment = format!("{}{}\n", self.comment, &comment[1..]);
line = line_start;
}
// e.g. quoted "deb" is not accepted by APT, so no need for quote word parsing here
line = match line.split_once(|c| char::is_ascii_whitespace(&c)) {
Some((package_type, rest)) => {
repo.types.push(package_type.try_into()?);
rest
}
None => return Ok(None), // empty line
};
line = line.trim_start_matches(|c| char::is_ascii_whitespace(&c));
let has_options = match line.strip_prefix('[') {
Some(rest) => {
// avoid the start of the options to be interpreted as the start of a quote word
line = rest;
true
}
None => false,
};
let mut tokens = SplitQuoteWord::new(line.to_string());
if has_options {
Self::parse_options(&mut repo.options, &mut tokens)?;
}
// the rest of the line is just '<uri> <suite> [<components>...]'
repo.uris
.push(tokens.next().ok_or_else(|| format_err!("missing URI"))??);
repo.suites.push(
tokens
.next()
.ok_or_else(|| format_err!("missing suite"))??,
);
for token in tokens {
repo.components.push(token?);
}
repo.comment = std::mem::take(&mut self.comment);
Ok(Some(repo))
}
}
impl<R: BufRead> APTRepositoryParser for APTListFileParser<R> {
fn parse_repositories(&mut self) -> Result<Vec<APTRepository>, Error> {
let mut repos = vec![];
let mut line = String::new();
loop {
self.line_nr += 1;
line.clear();
match self.input.read_line(&mut line) {
Err(err) => bail!("input error - {}", err),
Ok(0) => break,
Ok(_) => match self.parse_one_line(&line) {
Ok(Some(repo)) => repos.push(repo),
Ok(None) => continue,
Err(err) => bail!("malformed entry on line {} - {}", self.line_nr, err),
},
}
}
Ok(repos)
}
}

View File

@ -1,203 +0,0 @@
use std::io::BufRead;
use std::iter::Iterator;
use anyhow::{bail, Error};
use crate::repositories::{
APTRepository, APTRepositoryFileType, APTRepositoryOption, APTRepositoryPackageType,
};
use super::APTRepositoryParser;
pub struct APTSourcesFileParser<R: BufRead> {
input: R,
stanza_nr: usize,
comment: String,
}
/// See `man sources.list` and `man deb822` for the format specification.
impl<R: BufRead> APTSourcesFileParser<R> {
pub fn new(reader: R) -> Self {
Self {
input: reader,
stanza_nr: 1,
comment: String::new(),
}
}
/// Based on APT's `StringToBool` in `strutl.cc`
fn string_to_bool(string: &str, default: bool) -> bool {
let string = string.trim_matches(|c| char::is_ascii_whitespace(&c));
let string = string.to_lowercase();
match &string[..] {
"1" | "yes" | "true" | "with" | "on" | "enable" => true,
"0" | "no" | "false" | "without" | "off" | "disable" => false,
_ => default,
}
}
/// Checks if `key` is valid according to deb822
fn valid_key(key: &str) -> bool {
if key.starts_with('-') {
return false;
};
return key.chars().all(|c| matches!(c, '!'..='9' | ';'..='~'));
}
/// Try parsing a repository in stanza format from `lines`.
///
/// Returns `Ok(None)` when no stanza can be found.
///
/// Comments are added to `self.comments`. If a stanza can be found,
/// `self.comment` is added to the repository's `comment` property.
///
/// Fully commented out stanzas are treated as comments.
fn parse_stanza(&mut self, lines: &str) -> Result<Option<APTRepository>, Error> {
let mut repo = APTRepository::new(APTRepositoryFileType::Sources);
// Values may be folded into multiple lines.
// Those lines have to start with a space or a tab.
let lines = lines.replace("\n ", " ");
let lines = lines.replace("\n\t", " ");
let mut got_something = false;
for line in lines.lines() {
let line = line.trim_matches(|c| char::is_ascii_whitespace(&c));
if line.is_empty() {
continue;
}
if let Some(commented_out) = line.strip_prefix('#') {
self.comment = format!("{}{}\n", self.comment, commented_out);
continue;
}
if let Some(mid) = line.find(':') {
let (key, value_str) = line.split_at(mid);
let value_str = &value_str[1..];
let key = key.trim_matches(|c| char::is_ascii_whitespace(&c));
if key.is_empty() {
bail!("option has no key: '{}'", line);
}
if value_str.is_empty() {
// ignored by APT
eprintln!("option has no value: '{}'", line);
continue;
}
if !Self::valid_key(key) {
// ignored by APT
eprintln!("option with invalid key '{}'", key);
continue;
}
let values: Vec<String> = value_str
.split_ascii_whitespace()
.map(|value| value.to_string())
.collect();
match &key.to_lowercase()[..] {
"types" => {
if !repo.types.is_empty() {
eprintln!("key 'Types' was defined twice");
}
let mut types = Vec::<APTRepositoryPackageType>::new();
for package_type in values {
types.push((&package_type[..]).try_into()?);
}
repo.types = types;
}
"uris" => {
if !repo.uris.is_empty() {
eprintln!("key 'URIs' was defined twice");
}
repo.uris = values;
}
"suites" => {
if !repo.suites.is_empty() {
eprintln!("key 'Suites' was defined twice");
}
repo.suites = values;
}
"components" => {
if !repo.components.is_empty() {
eprintln!("key 'Components' was defined twice");
}
repo.components = values;
}
"enabled" => {
repo.set_enabled(Self::string_to_bool(value_str, true));
}
_ => repo.options.push(APTRepositoryOption {
key: key.to_string(),
values,
}),
}
} else {
bail!("got invalid line - '{:?}'", line);
}
got_something = true;
}
if !got_something {
return Ok(None);
}
repo.comment = std::mem::take(&mut self.comment);
Ok(Some(repo))
}
/// Helper function for `parse_repositories`.
fn try_parse_stanza(
&mut self,
lines: &str,
repos: &mut Vec<APTRepository>,
) -> Result<(), Error> {
match self.parse_stanza(lines) {
Ok(Some(repo)) => {
repos.push(repo);
self.stanza_nr += 1;
}
Ok(None) => (),
Err(err) => bail!("malformed entry in stanza {} - {}", self.stanza_nr, err),
}
Ok(())
}
}
impl<R: BufRead> APTRepositoryParser for APTSourcesFileParser<R> {
fn parse_repositories(&mut self) -> Result<Vec<APTRepository>, Error> {
let mut repos = vec![];
let mut lines = String::new();
loop {
let old_length = lines.len();
match self.input.read_line(&mut lines) {
Err(err) => bail!("input error - {}", err),
Ok(0) => {
self.try_parse_stanza(&lines[..], &mut repos)?;
break;
}
Ok(_) => {
if (lines[old_length..])
.trim_matches(|c| char::is_ascii_whitespace(&c))
.is_empty()
{
// detected end of stanza
self.try_parse_stanza(&lines[..], &mut repos)?;
lines.clear();
}
}
}
}
Ok(repos)
}
}

View File

@ -1,191 +0,0 @@
use std::collections::BTreeMap;
use std::path::PathBuf;
use anyhow::{bail, Error};
mod repository;
pub use repository::{
APTRepository, APTRepositoryFileType, APTRepositoryOption, APTRepositoryPackageType,
};
mod file;
pub use file::{APTRepositoryFile, APTRepositoryFileError, APTRepositoryInfo};
mod release;
pub use release::{get_current_release_codename, DebianCodename};
mod standard;
pub use standard::{APTRepositoryHandle, APTStandardRepository};
const APT_SOURCES_LIST_FILENAME: &str = "/etc/apt/sources.list";
const APT_SOURCES_LIST_DIRECTORY: &str = "/etc/apt/sources.list.d/";
/// Calculates a common digest for successfully parsed repository files.
///
/// The digest is invariant with respect to file order.
///
/// Files without a digest are ignored.
fn common_digest(files: &[APTRepositoryFile]) -> [u8; 32] {
let mut digests = BTreeMap::new();
for file in files.iter() {
digests.insert(file.path.clone(), &file.digest);
}
let mut common_raw = Vec::<u8>::with_capacity(digests.len() * 32);
for digest in digests.values() {
match digest {
Some(digest) => common_raw.extend_from_slice(&digest[..]),
None => (),
}
}
openssl::sha::sha256(&common_raw[..])
}
/// Provides additional information about the repositories.
///
/// The kind of information can be:
/// `warnings` for bad suites.
/// `ignore-pre-upgrade-warning` when the next stable suite is configured.
/// `badge` for official URIs.
pub fn check_repositories(
files: &[APTRepositoryFile],
current_suite: DebianCodename,
) -> Vec<APTRepositoryInfo> {
let mut infos = vec![];
for file in files.iter() {
infos.append(&mut file.check_suites(current_suite));
infos.append(&mut file.check_uris());
}
infos
}
/// Get the repository associated to the handle and the path where it is usually configured.
pub fn get_standard_repository(
handle: APTRepositoryHandle,
product: &str,
suite: DebianCodename,
) -> (APTRepository, String) {
let repo = handle.to_repository(product, &suite.to_string());
let path = handle.path(product);
(repo, path)
}
/// Return handles for standard Proxmox repositories and their status, where
/// `None` means not configured, and `Some(bool)` indicates enabled or disabled.
pub fn standard_repositories(
files: &[APTRepositoryFile],
product: &str,
suite: DebianCodename,
) -> Vec<APTStandardRepository> {
let mut result = vec![
APTStandardRepository::from(APTRepositoryHandle::Enterprise),
APTStandardRepository::from(APTRepositoryHandle::NoSubscription),
APTStandardRepository::from(APTRepositoryHandle::Test),
];
if product == "pve" {
result.append(&mut vec![
APTStandardRepository::from(APTRepositoryHandle::CephQuincy),
APTStandardRepository::from(APTRepositoryHandle::CephQuincyTest),
APTStandardRepository::from(APTRepositoryHandle::CephPacific),
APTStandardRepository::from(APTRepositoryHandle::CephPacificTest),
APTStandardRepository::from(APTRepositoryHandle::CephOctopus),
APTStandardRepository::from(APTRepositoryHandle::CephOctopusTest),
]);
}
for file in files.iter() {
for repo in file.repositories.iter() {
for entry in result.iter_mut() {
if entry.status == Some(true) {
continue;
}
if repo.is_referenced_repository(entry.handle, product, &suite.to_string())
|| repo.is_referenced_repository(
entry.handle,
product,
&suite.next().unwrap().to_string(),
) {
entry.status = Some(repo.enabled);
}
}
}
}
result
}
/// Returns all APT repositories configured in `/etc/apt/sources.list` and
/// in `/etc/apt/sources.list.d` including disabled repositories.
///
/// Returns the succesfully parsed files, a list of errors for files that could
/// not be read or parsed and a common digest for the succesfully parsed files.
///
/// The digest is guaranteed to be set for each successfully parsed file.
pub fn repositories() -> Result<
(
Vec<APTRepositoryFile>,
Vec<APTRepositoryFileError>,
[u8; 32],
),
Error,
> {
let to_result = |files: Vec<APTRepositoryFile>, errors: Vec<APTRepositoryFileError>| {
let common_digest = common_digest(&files);
(files, errors, common_digest)
};
let mut files = vec![];
let mut errors = vec![];
let sources_list_path = PathBuf::from(APT_SOURCES_LIST_FILENAME);
let sources_list_d_path = PathBuf::from(APT_SOURCES_LIST_DIRECTORY);
match APTRepositoryFile::new(sources_list_path) {
Ok(Some(mut file)) => match file.parse() {
Ok(()) => files.push(file),
Err(err) => errors.push(err),
},
_ => bail!("internal error with '{}'", APT_SOURCES_LIST_FILENAME),
}
if !sources_list_d_path.exists() {
return Ok(to_result(files, errors));
}
if !sources_list_d_path.is_dir() {
errors.push(APTRepositoryFileError {
path: APT_SOURCES_LIST_DIRECTORY.to_string(),
error: "not a directory!".to_string(),
});
return Ok(to_result(files, errors));
}
for entry in std::fs::read_dir(sources_list_d_path)? {
let path = entry?.path();
match APTRepositoryFile::new(path) {
Ok(Some(mut file)) => match file.parse() {
Ok(()) => {
if file.digest.is_none() {
bail!("internal error - digest not set");
}
files.push(file);
}
Err(err) => errors.push(err),
},
Ok(None) => (),
Err(err) => errors.push(err),
}
}
Ok(to_result(files, errors))
}

View File

@ -1,100 +0,0 @@
use std::fmt::Display;
use std::io::{BufRead, BufReader};
use anyhow::{bail, format_err, Error};
/// The code names of Debian releases. Does not include `sid`.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum DebianCodename {
Lenny = 5,
Squeeze,
Wheezy,
Jessie,
Stretch,
Buster,
Bullseye,
Bookworm,
Trixie,
}
impl DebianCodename {
pub fn next(&self) -> Option<Self> {
match (*self as u8 + 1).try_into() {
Ok(codename) => Some(codename),
Err(_) => None,
}
}
}
impl TryFrom<&str> for DebianCodename {
type Error = Error;
fn try_from(string: &str) -> Result<Self, Error> {
match string {
"lenny" => Ok(DebianCodename::Lenny),
"squeeze" => Ok(DebianCodename::Squeeze),
"wheezy" => Ok(DebianCodename::Wheezy),
"jessie" => Ok(DebianCodename::Jessie),
"stretch" => Ok(DebianCodename::Stretch),
"buster" => Ok(DebianCodename::Buster),
"bullseye" => Ok(DebianCodename::Bullseye),
"bookworm" => Ok(DebianCodename::Bookworm),
"trixie" => Ok(DebianCodename::Trixie),
_ => bail!("unknown Debian code name '{}'", string),
}
}
}
impl TryFrom<u8> for DebianCodename {
type Error = Error;
fn try_from(number: u8) -> Result<Self, Error> {
match number {
5 => Ok(DebianCodename::Lenny),
6 => Ok(DebianCodename::Squeeze),
7 => Ok(DebianCodename::Wheezy),
8 => Ok(DebianCodename::Jessie),
9 => Ok(DebianCodename::Stretch),
10 => Ok(DebianCodename::Buster),
11 => Ok(DebianCodename::Bullseye),
12 => Ok(DebianCodename::Bookworm),
13 => Ok(DebianCodename::Trixie),
_ => bail!("unknown Debian release number '{}'", number),
}
}
}
impl Display for DebianCodename {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DebianCodename::Lenny => write!(f, "lenny"),
DebianCodename::Squeeze => write!(f, "squeeze"),
DebianCodename::Wheezy => write!(f, "wheezy"),
DebianCodename::Jessie => write!(f, "jessie"),
DebianCodename::Stretch => write!(f, "stretch"),
DebianCodename::Buster => write!(f, "buster"),
DebianCodename::Bullseye => write!(f, "bullseye"),
DebianCodename::Bookworm => write!(f, "bookworm"),
DebianCodename::Trixie => write!(f, "trixie"),
}
}
}
/// Read the `VERSION_CODENAME` from `/etc/os-release`.
pub fn get_current_release_codename() -> Result<DebianCodename, Error> {
let raw = std::fs::read("/etc/os-release")
.map_err(|err| format_err!("unable to read '/etc/os-release' - {}", err))?;
let reader = BufReader::new(&*raw);
for line in reader.lines() {
let line = line.map_err(|err| format_err!("unable to read '/etc/os-release' - {}", err))?;
if let Some(codename) = line.strip_prefix("VERSION_CODENAME=") {
let codename = codename.trim_matches(&['"', '\''][..]);
return codename.try_into();
}
}
bail!("unable to parse codename from '/etc/os-release'");
}

View File

@ -1,555 +0,0 @@
use std::fmt::Display;
use std::io::{BufRead, BufReader, Write};
use std::path::PathBuf;
use anyhow::{bail, format_err, Error};
use serde::{Deserialize, Serialize};
use proxmox_schema::api;
use crate::repositories::standard::APTRepositoryHandle;
#[api]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum APTRepositoryFileType {
/// One-line-style format
List,
/// DEB822-style format
Sources,
}
impl TryFrom<&str> for APTRepositoryFileType {
type Error = Error;
fn try_from(string: &str) -> Result<Self, Error> {
match string {
"list" => Ok(APTRepositoryFileType::List),
"sources" => Ok(APTRepositoryFileType::Sources),
_ => bail!("invalid file type '{}'", string),
}
}
}
impl Display for APTRepositoryFileType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
APTRepositoryFileType::List => write!(f, "list"),
APTRepositoryFileType::Sources => write!(f, "sources"),
}
}
}
#[api]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum APTRepositoryPackageType {
/// Debian package
Deb,
/// Debian source package
DebSrc,
}
impl TryFrom<&str> for APTRepositoryPackageType {
type Error = Error;
fn try_from(string: &str) -> Result<Self, Error> {
match string {
"deb" => Ok(APTRepositoryPackageType::Deb),
"deb-src" => Ok(APTRepositoryPackageType::DebSrc),
_ => bail!("invalid package type '{}'", string),
}
}
}
impl Display for APTRepositoryPackageType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
APTRepositoryPackageType::Deb => write!(f, "deb"),
APTRepositoryPackageType::DebSrc => write!(f, "deb-src"),
}
}
}
#[api(
properties: {
Key: {
description: "Option key.",
type: String,
},
Values: {
description: "Option values.",
type: Array,
items: {
description: "Value.",
type: String,
},
},
},
)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")] // for consistency
/// Additional options for an APT repository.
/// Used for both single- and mutli-value options.
pub struct APTRepositoryOption {
/// Option key.
pub key: String,
/// Option value(s).
pub values: Vec<String>,
}
#[api(
properties: {
Types: {
description: "List of package types.",
type: Array,
items: {
type: APTRepositoryPackageType,
},
},
URIs: {
description: "List of repository URIs.",
type: Array,
items: {
description: "Repository URI.",
type: String,
},
},
Suites: {
description: "List of distributions.",
type: Array,
items: {
description: "Package distribution.",
type: String,
},
},
Components: {
description: "List of repository components.",
type: Array,
items: {
description: "Repository component.",
type: String,
},
},
Options: {
type: Array,
optional: true,
items: {
type: APTRepositoryOption,
},
},
Comment: {
description: "Associated comment.",
type: String,
optional: true,
},
FileType: {
type: APTRepositoryFileType,
},
Enabled: {
description: "Whether the repository is enabled or not.",
type: Boolean,
},
},
)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
/// Describes an APT repository.
pub struct APTRepository {
/// List of package types.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub types: Vec<APTRepositoryPackageType>,
/// List of repository URIs.
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(rename = "URIs")]
pub uris: Vec<String>,
/// List of package distributions.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub suites: Vec<String>,
/// List of repository components.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub components: Vec<String>,
/// Additional options.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub options: Vec<APTRepositoryOption>,
/// Associated comment.
#[serde(skip_serializing_if = "String::is_empty")]
pub comment: String,
/// Format of the defining file.
pub file_type: APTRepositoryFileType,
/// Whether the repository is enabled or not.
pub enabled: bool,
}
impl APTRepository {
/// Crates an empty repository.
pub fn new(file_type: APTRepositoryFileType) -> Self {
Self {
types: vec![],
uris: vec![],
suites: vec![],
components: vec![],
options: vec![],
comment: String::new(),
file_type,
enabled: true,
}
}
/// Changes the `enabled` flag and makes sure the `Enabled` option for
/// `APTRepositoryPackageType::Sources` repositories is updated too.
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
if self.file_type == APTRepositoryFileType::Sources {
let enabled_string = match enabled {
true => "true".to_string(),
false => "false".to_string(),
};
for option in self.options.iter_mut() {
if option.key == "Enabled" {
option.values = vec![enabled_string];
return;
}
}
self.options.push(APTRepositoryOption {
key: "Enabled".to_string(),
values: vec![enabled_string],
});
}
}
/// Makes sure that all basic properties of a repository are present and
/// not obviously invalid.
pub fn basic_check(&self) -> Result<(), Error> {
if self.types.is_empty() {
bail!("missing package type(s)");
}
if self.uris.is_empty() {
bail!("missing URI(s)");
}
if self.suites.is_empty() {
bail!("missing suite(s)");
}
for uri in self.uris.iter() {
if !uri.contains(':') || uri.len() < 3 {
bail!("invalid URI: '{}'", uri);
}
}
for suite in self.suites.iter() {
if !suite.ends_with('/') && self.components.is_empty() {
bail!("missing component(s)");
} else if suite.ends_with('/') && !self.components.is_empty() {
bail!("absolute suite '{}' does not allow component(s)", suite);
}
}
if self.file_type == APTRepositoryFileType::List {
if self.types.len() > 1 {
bail!("more than one package type");
}
if self.uris.len() > 1 {
bail!("more than one URI");
}
if self.suites.len() > 1 {
bail!("more than one suite");
}
}
Ok(())
}
/// Checks if the repository is the one referenced by the handle.
pub fn is_referenced_repository(
&self,
handle: APTRepositoryHandle,
product: &str,
suite: &str,
) -> bool {
let (package_type, handle_uris, component) = handle.info(product);
let mut found_uri = false;
for uri in self.uris.iter() {
let uri = uri.trim_end_matches('/');
found_uri = found_uri || handle_uris.iter().any(|handle_uri| handle_uri == uri);
}
self.types.contains(&package_type)
&& found_uri
// using contains would require a &String
&& self.suites.iter().any(|self_suite| self_suite == suite)
&& self.components.contains(&component)
}
/// Guess the origin from the repository's URIs.
///
/// Intended to be used as a fallback for get_cached_origin.
pub fn origin_from_uris(&self) -> Option<String> {
for uri in self.uris.iter() {
if let Some(host) = host_from_uri(uri) {
if host == "proxmox.com" || host.ends_with(".proxmox.com") {
return Some("Proxmox".to_string());
}
if host == "debian.org" || host.ends_with(".debian.org") {
return Some("Debian".to_string());
}
}
}
None
}
/// Get the `Origin:` value from a cached InRelease file.
pub fn get_cached_origin(&self) -> Result<Option<String>, Error> {
for uri in self.uris.iter() {
for suite in self.suites.iter() {
let file = in_release_filename(uri, suite);
if !file.exists() {
continue;
}
let raw = std::fs::read(&file)
.map_err(|err| format_err!("unable to read {:?} - {}", file, err))?;
let reader = BufReader::new(&*raw);
for line in reader.lines() {
let line =
line.map_err(|err| format_err!("unable to read {:?} - {}", file, err))?;
if let Some(value) = line.strip_prefix("Origin:") {
return Ok(Some(
value
.trim_matches(|c| char::is_ascii_whitespace(&c))
.to_string(),
));
}
}
}
}
Ok(None)
}
/// Writes a repository in the corresponding format followed by a blank.
///
/// Expects that `basic_check()` for the repository was successful.
pub fn write(&self, w: &mut dyn Write) -> Result<(), Error> {
match self.file_type {
APTRepositoryFileType::List => write_one_line(self, w),
APTRepositoryFileType::Sources => write_stanza(self, w),
}
}
}
/// Get the path to the cached InRelease file.
fn in_release_filename(uri: &str, suite: &str) -> PathBuf {
let mut path = PathBuf::from(&crate::config::get().dir_state);
path.push(&crate::config::get().dir_state_lists);
let filename = uri_to_filename(uri);
path.push(format!(
"{}_dists_{}_InRelease",
filename,
suite.replace('/', "_"), // e.g. for buster/updates
));
path
}
/// See APT's URItoFileName in contrib/strutl.cc
fn uri_to_filename(uri: &str) -> String {
let mut filename = uri;
if let Some(begin) = filename.find("://") {
filename = &filename[(begin + 3)..];
}
if uri.starts_with("http://") || uri.starts_with("https://") {
if let Some(begin) = filename.find('@') {
filename = &filename[(begin + 1)..];
}
}
// APT seems to only strip one final slash, so do the same
filename = filename.strip_suffix('/').unwrap_or(filename);
let encode_chars = "\\|{}[]<>\"^~_=!@#$%^&*";
let mut encoded = String::with_capacity(filename.len());
for b in filename.as_bytes().iter() {
if *b <= 0x20 || *b >= 0x7F || encode_chars.contains(*b as char) {
let mut hex = [0u8; 2];
// unwrap: we're hex-encoding a single byte into a 2-byte slice
hex::encode_to_slice(&[*b], &mut hex).unwrap();
let hex = unsafe { std::str::from_utf8_unchecked(&hex) };
encoded = format!("{}%{}", encoded, hex);
} else {
encoded.push(*b as char);
}
}
encoded.replace('/', "_")
}
/// Get the host part from a given URI.
fn host_from_uri(uri: &str) -> Option<&str> {
let host = uri.strip_prefix("http")?;
let host = host.strip_prefix('s').unwrap_or(host);
let mut host = host.strip_prefix("://")?;
if let Some(end) = host.find('/') {
host = &host[..end];
}
if let Some(begin) = host.find('@') {
host = &host[(begin + 1)..];
}
if let Some(end) = host.find(':') {
host = &host[..end];
}
Some(host)
}
/// Strips existing double quotes from the string first, and then adds double quotes at
/// the beginning and end if there is an ASCII whitespace in the `string`, which is not
/// escaped by `[]`.
fn quote_for_one_line(string: &str) -> String {
let mut add_quotes = false;
let mut wait_for_bracket = false;
// easier to just quote the whole string, so ignore pre-existing quotes
// currently, parsing removes them anyways, but being on the safe side is rather cheap
let string = string.replace('"', "");
for c in string.chars() {
if wait_for_bracket {
if c == ']' {
wait_for_bracket = false;
}
continue;
}
if char::is_ascii_whitespace(&c) {
add_quotes = true;
break;
}
if c == '[' {
wait_for_bracket = true;
}
}
match add_quotes {
true => format!("\"{}\"", string),
false => string,
}
}
/// Writes a repository in one-line format followed by a blank line.
///
/// Expects that `repo.file_type == APTRepositoryFileType::List`.
///
/// Expects that `basic_check()` for the repository was successful.
fn write_one_line(repo: &APTRepository, w: &mut dyn Write) -> Result<(), Error> {
if repo.file_type != APTRepositoryFileType::List {
bail!("not a .list repository");
}
if !repo.comment.is_empty() {
for line in repo.comment.lines() {
writeln!(w, "#{}", line)?;
}
}
if !repo.enabled {
write!(w, "# ")?;
}
write!(w, "{} ", repo.types[0])?;
if !repo.options.is_empty() {
write!(w, "[ ")?;
for option in repo.options.iter() {
let option = quote_for_one_line(&format!("{}={}", option.key, option.values.join(",")));
write!(w, "{} ", option)?;
}
write!(w, "] ")?;
};
write!(w, "{} ", quote_for_one_line(&repo.uris[0]))?;
write!(w, "{} ", quote_for_one_line(&repo.suites[0]))?;
writeln!(
w,
"{}",
repo.components
.iter()
.map(|comp| quote_for_one_line(comp))
.collect::<Vec<String>>()
.join(" ")
)?;
writeln!(w)?;
Ok(())
}
/// Writes a single stanza followed by a blank line.
///
/// Expects that `repo.file_type == APTRepositoryFileType::Sources`.
fn write_stanza(repo: &APTRepository, w: &mut dyn Write) -> Result<(), Error> {
if repo.file_type != APTRepositoryFileType::Sources {
bail!("not a .sources repository");
}
if !repo.comment.is_empty() {
for line in repo.comment.lines() {
writeln!(w, "#{}", line)?;
}
}
write!(w, "Types:")?;
repo.types
.iter()
.try_for_each(|package_type| write!(w, " {}", package_type))?;
writeln!(w)?;
writeln!(w, "URIs: {}", repo.uris.join(" "))?;
writeln!(w, "Suites: {}", repo.suites.join(" "))?;
if !repo.components.is_empty() {
writeln!(w, "Components: {}", repo.components.join(" "))?;
}
for option in repo.options.iter() {
writeln!(w, "{}: {}", option.key, option.values.join(" "))?;
}
writeln!(w)?;
Ok(())
}
#[test]
fn test_uri_to_filename() {
let filename = uri_to_filename("https://some_host/some/path");
assert_eq!(filename, "some%5fhost_some_path".to_string());
}

View File

@ -1,273 +0,0 @@
use std::fmt::Display;
use anyhow::{bail, Error};
use serde::{Deserialize, Serialize};
use crate::repositories::repository::{
APTRepository, APTRepositoryFileType, APTRepositoryPackageType,
};
use proxmox_schema::api;
#[api(
properties: {
handle: {
description: "Handle referencing a standard repository.",
type: String,
},
},
)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
/// Reference to a standard repository and configuration status.
pub struct APTStandardRepository {
/// Handle referencing a standard repository.
pub handle: APTRepositoryHandle,
/// Configuration status of the associated repository.
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<bool>,
/// Display name of the repository.
pub name: String,
/// Description of the repository.
pub description: String,
}
#[api]
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
/// Handles for Proxmox repositories.
pub enum APTRepositoryHandle {
/// The enterprise repository for production use.
Enterprise,
/// The repository that can be used without subscription.
NoSubscription,
/// The test repository.
Test,
/// Ceph Quincy repository.
CephQuincy,
/// Ceph Quincy test repository.
CephQuincyTest,
/// Ceph Pacific repository.
CephPacific,
/// Ceph Pacific test repository.
CephPacificTest,
/// Ceph Octoput repository.
CephOctopus,
/// Ceph Octoput test repository.
CephOctopusTest,
}
impl From<APTRepositoryHandle> for APTStandardRepository {
fn from(handle: APTRepositoryHandle) -> Self {
APTStandardRepository {
handle,
status: None,
name: handle.name(),
description: handle.description(),
}
}
}
impl TryFrom<&str> for APTRepositoryHandle {
type Error = Error;
fn try_from(string: &str) -> Result<Self, Error> {
match string {
"enterprise" => Ok(APTRepositoryHandle::Enterprise),
"no-subscription" => Ok(APTRepositoryHandle::NoSubscription),
"test" => Ok(APTRepositoryHandle::Test),
"ceph-quincy" => Ok(APTRepositoryHandle::CephQuincy),
"ceph-quincy-test" => Ok(APTRepositoryHandle::CephQuincyTest),
"ceph-pacific" => Ok(APTRepositoryHandle::CephPacific),
"ceph-pacific-test" => Ok(APTRepositoryHandle::CephPacificTest),
"ceph-octopus" => Ok(APTRepositoryHandle::CephOctopus),
"ceph-octopus-test" => Ok(APTRepositoryHandle::CephOctopusTest),
_ => bail!("unknown repository handle '{}'", string),
}
}
}
impl Display for APTRepositoryHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
APTRepositoryHandle::Enterprise => write!(f, "enterprise"),
APTRepositoryHandle::NoSubscription => write!(f, "no-subscription"),
APTRepositoryHandle::Test => write!(f, "test"),
APTRepositoryHandle::CephQuincy => write!(f, "ceph-quincy"),
APTRepositoryHandle::CephQuincyTest => write!(f, "ceph-quincy-test"),
APTRepositoryHandle::CephPacific => write!(f, "ceph-pacific"),
APTRepositoryHandle::CephPacificTest => write!(f, "ceph-pacific-test"),
APTRepositoryHandle::CephOctopus => write!(f, "ceph-octopus"),
APTRepositoryHandle::CephOctopusTest => write!(f, "ceph-octopus-test"),
}
}
}
impl APTRepositoryHandle {
/// Get the description for the repository.
pub fn description(self) -> String {
match self {
APTRepositoryHandle::Enterprise => {
"This is the default, stable, and recommended repository, available for all \
Proxmox subscription users."
}
APTRepositoryHandle::NoSubscription => {
"This is the recommended repository for testing and non-production use. \
Its packages are not as heavily tested and validated as the production ready \
enterprise repository. You don't need a subscription key to access this repository."
}
APTRepositoryHandle::Test => {
"This repository contains the latest packages and is primarily used for test labs \
and by developers to test new features."
}
APTRepositoryHandle::CephQuincy => {
"This repository holds the main Proxmox Ceph Quincy packages."
}
APTRepositoryHandle::CephQuincyTest => {
"This repository contains the Ceph Quincy packages before they are moved to the \
main repository."
}
APTRepositoryHandle::CephPacific => {
"This repository holds the main Proxmox Ceph Pacific packages."
}
APTRepositoryHandle::CephPacificTest => {
"This repository contains the Ceph Pacific packages before they are moved to the \
main repository."
}
APTRepositoryHandle::CephOctopus => {
"This repository holds the main Proxmox Ceph Octopus packages."
}
APTRepositoryHandle::CephOctopusTest => {
"This repository contains the Ceph Octopus packages before they are moved to the \
main repository."
}
}
.to_string()
}
/// Get the display name of the repository.
pub fn name(self) -> String {
match self {
APTRepositoryHandle::Enterprise => "Enterprise",
APTRepositoryHandle::NoSubscription => "No-Subscription",
APTRepositoryHandle::Test => "Test",
APTRepositoryHandle::CephQuincy => "Ceph Quincy",
APTRepositoryHandle::CephQuincyTest => "Ceph Quincy Test",
APTRepositoryHandle::CephPacific => "Ceph Pacific",
APTRepositoryHandle::CephPacificTest => "Ceph Pacific Test",
APTRepositoryHandle::CephOctopus => "Ceph Octopus",
APTRepositoryHandle::CephOctopusTest => "Ceph Octopus Test",
}
.to_string()
}
/// Get the standard file path for the repository referenced by the handle.
pub fn path(self, product: &str) -> String {
match self {
APTRepositoryHandle::Enterprise => {
format!("/etc/apt/sources.list.d/{}-enterprise.list", product)
}
APTRepositoryHandle::NoSubscription => "/etc/apt/sources.list".to_string(),
APTRepositoryHandle::Test => "/etc/apt/sources.list".to_string(),
APTRepositoryHandle::CephQuincy => "/etc/apt/sources.list.d/ceph.list".to_string(),
APTRepositoryHandle::CephQuincyTest => "/etc/apt/sources.list.d/ceph.list".to_string(),
APTRepositoryHandle::CephPacific => "/etc/apt/sources.list.d/ceph.list".to_string(),
APTRepositoryHandle::CephPacificTest => "/etc/apt/sources.list.d/ceph.list".to_string(),
APTRepositoryHandle::CephOctopus => "/etc/apt/sources.list.d/ceph.list".to_string(),
APTRepositoryHandle::CephOctopusTest => "/etc/apt/sources.list.d/ceph.list".to_string(),
}
}
/// Get package type, possible URIs and the component associated with the handle.
///
/// The first URI is the preferred one.
pub fn info(self, product: &str) -> (APTRepositoryPackageType, Vec<String>, String) {
match self {
APTRepositoryHandle::Enterprise => (
APTRepositoryPackageType::Deb,
match product {
"pve" => vec![
"https://enterprise.proxmox.com/debian/pve".to_string(),
"https://enterprise.proxmox.com/debian".to_string(),
],
_ => vec![format!("https://enterprise.proxmox.com/debian/{}", product)],
},
format!("{}-enterprise", product),
),
APTRepositoryHandle::NoSubscription => (
APTRepositoryPackageType::Deb,
match product {
"pve" => vec![
"http://download.proxmox.com/debian/pve".to_string(),
"http://download.proxmox.com/debian".to_string(),
],
_ => vec![format!("http://download.proxmox.com/debian/{}", product)],
},
format!("{}-no-subscription", product),
),
APTRepositoryHandle::Test => (
APTRepositoryPackageType::Deb,
match product {
"pve" => vec![
"http://download.proxmox.com/debian/pve".to_string(),
"http://download.proxmox.com/debian".to_string(),
],
_ => vec![format!("http://download.proxmox.com/debian/{}", product)],
},
format!("{}test", product),
),
APTRepositoryHandle::CephQuincy => (
APTRepositoryPackageType::Deb,
vec!["http://download.proxmox.com/debian/ceph-quincy".to_string()],
"main".to_string(),
),
APTRepositoryHandle::CephQuincyTest => (
APTRepositoryPackageType::Deb,
vec!["http://download.proxmox.com/debian/ceph-quincy".to_string()],
"test".to_string(),
),
APTRepositoryHandle::CephPacific => (
APTRepositoryPackageType::Deb,
vec!["http://download.proxmox.com/debian/ceph-pacific".to_string()],
"main".to_string(),
),
APTRepositoryHandle::CephPacificTest => (
APTRepositoryPackageType::Deb,
vec!["http://download.proxmox.com/debian/ceph-pacific".to_string()],
"test".to_string(),
),
APTRepositoryHandle::CephOctopus => (
APTRepositoryPackageType::Deb,
vec!["http://download.proxmox.com/debian/ceph-octopus".to_string()],
"main".to_string(),
),
APTRepositoryHandle::CephOctopusTest => (
APTRepositoryPackageType::Deb,
vec!["http://download.proxmox.com/debian/ceph-octopus".to_string()],
"test".to_string(),
),
}
}
/// Get the standard repository referenced by the handle.
///
/// An URI in the result is not '/'-terminated (under the assumption that no valid
/// product name is).
pub fn to_repository(self, product: &str, suite: &str) -> APTRepository {
let (package_type, uris, component) = self.info(product);
APTRepository {
types: vec![package_type],
uris: vec![uris.into_iter().next().unwrap()],
suites: vec![suite.to_string()],
components: vec![component],
options: vec![],
comment: String::new(),
file_type: APTRepositoryFileType::List,
enabled: true,
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,38 +0,0 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Architectures: amd64
Codename: bullseye
Components: pvetest
Date: Mon, 28 Jun 2021 18:05:11 +0000
Label: Proxmox Debian Repository
Origin: Proxmox
Suite: stable
MD5Sum:
9bea8f79b808979720721f86da15b8c5 163424 pvetest/binary-amd64/Packages
a3286322ee6bfd3d5fec80d7c665f9c0 48621 pvetest/binary-amd64/Packages.gz
SHA1:
80fcafa4bf4a0c3c61c45d3b2fabc44d87772d42 163424 pvetest/binary-amd64/Packages
8ddbdacf5c6e4543300650e9abbfd91557ebae17 48621 pvetest/binary-amd64/Packages.gz
SHA256:
2b37f06ef01b4735db37a87f426c8042d2dce0fe3e571ba2a70259ef0d25d37b 163424 pvetest/binary-amd64/Packages
86cd183c6684d620dbea9419ac4585d2e6d35e2ecd83ccf3b63cd638d7fec93d 48621 pvetest/binary-amd64/Packages.gz
SHA512:
d2095e4a621159d339682a6340ba3fd9ad32ec2c5c25f39594f8abea803f5c90dfc8c161e1ab3bfd0ec0830ae00165ffb219e18be2e33a8be17c68b86d187ddf 163424 pvetest/binary-amd64/Packages
5af312c9b85252a071d24b434585579980ce28ee27073f076b78f7085b102d089026ded32ea9a00589f72e328362b42ee2efaf7282459d532be52e1766b8b007 48621 pvetest/binary-amd64/Packages.gz
-----BEGIN PGP SIGNATURE-----
iQIzBAEBCAAdFiEEKBOaL4ML1oR4oaAf3UujkX4jv1kFAmDaD1sACgkQ3UujkX4j
v1nT4A/9EwXscYVy3TVw/t1h21EdP/ADvwmJVWJbmhENh7FPxvRNnr9Q7r/4ZcOr
//gYAWm7e6DaiZEye5R7aH7BKKr16a5LOnXRv0nDWNSj50U/1B3KReGHFgRDRmyJ
EZ+bg99PzyRzsD5UCjeMdbFvOBLiX8nK430jDg4ccRNdhxzYcu3L7Ds1GiS8C/kO
bjB0hEEStqK0V4Lj5ZTrDOa+qoj9IS1N9Y4+loi+iEHRfF3q6vCXnqz/ZH53ese5
SDbKVaBNFX4zsXfeowLV2tqWkuYBgzjOBLNtSEezlWLm83hVQ5cr7MB7zt1yPAYy
V5+J7gIthKpJy9HE0DJ5qf+aekc/jt+tMVkrpwmwEmMIpyzY3Rh4tvpoyqC5ccqt
roll1XlnUjjWzPGJUh2W0SGOnvKLb16p/yqDALIYLDgzjnP/B6iserrekWa15Cvz
/HyffedsRbJ/JBDxJ54rV5KoPbRUsXkAp/fMZqOZu2wOxBHaTLqnHtHVJC9+pnef
dXeJixX0Bb34vZPbYRRByUrv6DueqlS0M27YIdLoO1KIvRzQL4OWfni6Fu9DF3M6
Ux1JF10Q/IwksQKXAyTgiGsStqnAGz13m0PJ+/swwzHK+Ktj7OoikKVnHZUkuljr
easVE4GoqJEwuKC6nI0Dosluy96wkPo5KecsUUdcSeToeaad07U=
=/mk7
-----END PGP SIGNATURE-----

View File

@ -1,394 +0,0 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Origin: Debian
Label: Debian
Suite: testing-updates
Codename: bullseye-updates
Date: Tue, 29 Jun 2021 08:07:21 UTC
Valid-Until: Tue, 06 Jul 2021 08:07:21 UTC
Acquire-By-Hash: yes
No-Support-for-Architecture-all: Packages
Architectures: all amd64 arm64 armel armhf i386 mips mips64el mipsel ppc64el s390x
Components: main contrib non-free
Description: Debian x.y Testing distribution Updates - Not Released
SHA256:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-all
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-all.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-source
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-source.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-all
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-all.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-all/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-all/Packages.xz
68897647a7a7574bc3cbf2752a8197b599a92bb51594114dc309f189a57d2d76 112 contrib/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-amd64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-amd64/Packages.xz
1b82761373d3072d18c5da66c06c445463e3a96413df6801d67bc8f593994b4c 114 contrib/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-arm64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-arm64/Packages.xz
90a6100dbdb528f71c742453100ed389bf1889f230f97850ad447f5f1842ee66 114 contrib/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-armel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-armel/Packages.xz
160791b0d348565711501f5a64731aa163f4ffc264adf0941783b126edacd0fe 114 contrib/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-armhf/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-armhf/Packages.xz
f9c32c87b588d6cebbcc15ca1019e6079cd70d0bb470c349948044ac1d708ca9 114 contrib/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-i386/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-i386/Packages.xz
065ff088c1eb921afe428d9b9995c10ded4e1460eee96b0d18946f40a581d051 113 contrib/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-mips/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-mips/Packages.xz
371aa544d22e9bffa4743e38f48fcc39279371c17ecfc11506ab5e969d4209e9 113 contrib/binary-mips/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-mips64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-mips64el/Packages.xz
952b96752ea34a4236ed7d8e96931db30f5f1152be9e979421cbec33e1281d93 117 contrib/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-mipsel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-mipsel/Packages.xz
1269cc174396d41f61bee7ef18394c0cd55062c331392184a228f80e10a92e50 115 contrib/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-ppc64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-ppc64el/Packages.xz
9ceedee057d170036270046025f9763a90be0a0f622af390aacc5dc917cbe2aa 116 contrib/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-s390x/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-s390x/Packages.xz
bb3f0c6a788afab2b7996008c2b5306a374dc20b34873ea4b0ce267aaaea533c 114 contrib/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-all/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-all/Packages.xz
68897647a7a7574bc3cbf2752a8197b599a92bb51594114dc309f189a57d2d76 112 contrib/debian-installer/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-amd64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-amd64/Packages.xz
1b82761373d3072d18c5da66c06c445463e3a96413df6801d67bc8f593994b4c 114 contrib/debian-installer/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-arm64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-arm64/Packages.xz
90a6100dbdb528f71c742453100ed389bf1889f230f97850ad447f5f1842ee66 114 contrib/debian-installer/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-armel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-armel/Packages.xz
160791b0d348565711501f5a64731aa163f4ffc264adf0941783b126edacd0fe 114 contrib/debian-installer/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-armhf/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-armhf/Packages.xz
f9c32c87b588d6cebbcc15ca1019e6079cd70d0bb470c349948044ac1d708ca9 114 contrib/debian-installer/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-i386/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-i386/Packages.xz
065ff088c1eb921afe428d9b9995c10ded4e1460eee96b0d18946f40a581d051 113 contrib/debian-installer/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-mips/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-mips/Packages.xz
371aa544d22e9bffa4743e38f48fcc39279371c17ecfc11506ab5e969d4209e9 113 contrib/debian-installer/binary-mips/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-mips64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-mips64el/Packages.xz
952b96752ea34a4236ed7d8e96931db30f5f1152be9e979421cbec33e1281d93 117 contrib/debian-installer/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-mipsel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-mipsel/Packages.xz
1269cc174396d41f61bee7ef18394c0cd55062c331392184a228f80e10a92e50 115 contrib/debian-installer/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-ppc64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-ppc64el/Packages.xz
9ceedee057d170036270046025f9763a90be0a0f622af390aacc5dc917cbe2aa 116 contrib/debian-installer/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-s390x/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-s390x/Packages.xz
bb3f0c6a788afab2b7996008c2b5306a374dc20b34873ea4b0ce267aaaea533c 114 contrib/debian-installer/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/i18n/Translation-en
d3dda84eb03b9738d118eb2be78e246106900493c0ae07819ad60815134a8058 14 contrib/i18n/Translation-en.bz2
66ff0b3e39b97b69ae41026692402554a34da7523806e9484dadaa201d874826 115 contrib/source/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/source/Sources
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/source/Sources.xz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-all
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-all.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-source
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-source.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-all
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-all.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-all/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-all/Packages.xz
4ec10e1952b6b67f18ba86e49a9fdadae2702fc8dd9caf81fce3fa67b26933c1 109 main/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-amd64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-amd64/Packages.xz
11235baea1cc51f728b67e6a72b440eb5c45476bccd33d4c93308bc3ac5aebb3 111 main/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-arm64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-arm64/Packages.xz
f9638553772bb2f185419f349d1a0835ecc23074a1c867f64ed81b43b79cec85 111 main/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-armel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-armel/Packages.xz
e6dc2d69357d199629d876d48880f3fe4675aa3a7174ac868a58d7fa3853a9d3 111 main/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-armhf/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-armhf/Packages.xz
93df18bef47710a17488b390419821583b4894ef1639a7d89657e422bf5fc915 111 main/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-i386/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-i386/Packages.xz
a3458e3b58f39105b96c2ec4c92a977c398f53aa29f7838da3c537de78546a6a 110 main/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-mips/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-mips/Packages.xz
027758abbd72b9bb64fd321e4d5c5f6288fa20e376c11f4ecf8bf4ade3acb47c 110 main/binary-mips/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-mips64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-mips64el/Packages.xz
6fbea5916d47ff86a4b93e9643f078b59dcfaf3a25c2f5f2ba4e5416ce4c8117 114 main/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-mipsel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-mipsel/Packages.xz
f160fb8cee5639caf0f75a5c4aab1fff1262f824a9621fb45ca15dfa698baf03 112 main/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-ppc64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-ppc64el/Packages.xz
014cead9abbc4cce0ba6e078dce34a8d1a56e6b16b7bee1a463fd9f67eed2c33 113 main/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-s390x/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-s390x/Packages.xz
78b704e8437b918152687d47061e2211a5119015e2a13a0d6a46d31dcb32491b 111 main/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-all/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-all/Packages.xz
4ec10e1952b6b67f18ba86e49a9fdadae2702fc8dd9caf81fce3fa67b26933c1 109 main/debian-installer/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-amd64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-amd64/Packages.xz
11235baea1cc51f728b67e6a72b440eb5c45476bccd33d4c93308bc3ac5aebb3 111 main/debian-installer/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-arm64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-arm64/Packages.xz
f9638553772bb2f185419f349d1a0835ecc23074a1c867f64ed81b43b79cec85 111 main/debian-installer/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-armel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-armel/Packages.xz
e6dc2d69357d199629d876d48880f3fe4675aa3a7174ac868a58d7fa3853a9d3 111 main/debian-installer/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-armhf/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-armhf/Packages.xz
93df18bef47710a17488b390419821583b4894ef1639a7d89657e422bf5fc915 111 main/debian-installer/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-i386/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-i386/Packages.xz
a3458e3b58f39105b96c2ec4c92a977c398f53aa29f7838da3c537de78546a6a 110 main/debian-installer/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-mips/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-mips/Packages.xz
027758abbd72b9bb64fd321e4d5c5f6288fa20e376c11f4ecf8bf4ade3acb47c 110 main/debian-installer/binary-mips/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-mips64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-mips64el/Packages.xz
6fbea5916d47ff86a4b93e9643f078b59dcfaf3a25c2f5f2ba4e5416ce4c8117 114 main/debian-installer/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-mipsel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-mipsel/Packages.xz
f160fb8cee5639caf0f75a5c4aab1fff1262f824a9621fb45ca15dfa698baf03 112 main/debian-installer/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-ppc64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-ppc64el/Packages.xz
014cead9abbc4cce0ba6e078dce34a8d1a56e6b16b7bee1a463fd9f67eed2c33 113 main/debian-installer/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-s390x/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-s390x/Packages.xz
78b704e8437b918152687d47061e2211a5119015e2a13a0d6a46d31dcb32491b 111 main/debian-installer/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/i18n/Translation-en
d3dda84eb03b9738d118eb2be78e246106900493c0ae07819ad60815134a8058 14 main/i18n/Translation-en.bz2
008e62f7c379b27d3f00fe8ea8eaa139809f11900ca89d6617ccf934a9024142 112 main/source/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/source/Sources
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/source/Sources.xz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-all
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-all.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-source
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-source.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-all
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-all.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-all/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-all/Packages.xz
28dec0023644dd6907ab629efa820fdd61b10c19eb43ddf0d8737c5057e0c7b4 113 non-free/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-amd64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-amd64/Packages.xz
1b520c87f6a798699fc39e1d75ebac7e78fdd4a201b18426109f0f338f18e366 115 non-free/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-arm64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-arm64/Packages.xz
9d15a08c7396b78ed595c22f5d1cce574d4cf0dcda3d5458722270dce0ddf276 115 non-free/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-armel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-armel/Packages.xz
c19938ccb9743e0318b2bb3e7871a2347d74afadfd23e23f63465ed31d01e2db 115 non-free/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-armhf/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-armhf/Packages.xz
88c4a76de56dd43f88d925c471093ace5e7aac17a6d6a8b01135fb3cf6a1d80f 115 non-free/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-i386/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-i386/Packages.xz
bfab31be4f95d8f8eec40f7e7dfe7caad494a1496945989a0b5936d24125a711 114 non-free/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-mips/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-mips/Packages.xz
37a9b6efa8e5b0f9ee74f3202db161c209fa066830268ea30b206d8b1f9052c1 114 non-free/binary-mips/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-mips64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-mips64el/Packages.xz
d50cbb62225425908c57af80d918ccc99859ba4803be62b44da8d8159eb86847 118 non-free/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-mipsel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-mipsel/Packages.xz
a30e01cfad8b7f5e5f7115787590a2a3342ddea51857ccb230180e555b9c68a4 116 non-free/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-ppc64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-ppc64el/Packages.xz
33da686a19b40e79ba83a7daf8d863010c36ff9f6d063ecc698a91d3cf626f53 117 non-free/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-s390x/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-s390x/Packages.xz
acc91e4fa6121d24aba8b1e8f39cffde4512bb549044cbf88f998cd4dc0423df 115 non-free/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-all/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-all/Packages.xz
28dec0023644dd6907ab629efa820fdd61b10c19eb43ddf0d8737c5057e0c7b4 113 non-free/debian-installer/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-amd64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-amd64/Packages.xz
1b520c87f6a798699fc39e1d75ebac7e78fdd4a201b18426109f0f338f18e366 115 non-free/debian-installer/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-arm64/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-arm64/Packages.xz
9d15a08c7396b78ed595c22f5d1cce574d4cf0dcda3d5458722270dce0ddf276 115 non-free/debian-installer/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-armel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-armel/Packages.xz
c19938ccb9743e0318b2bb3e7871a2347d74afadfd23e23f63465ed31d01e2db 115 non-free/debian-installer/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-armhf/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-armhf/Packages.xz
88c4a76de56dd43f88d925c471093ace5e7aac17a6d6a8b01135fb3cf6a1d80f 115 non-free/debian-installer/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-i386/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-i386/Packages.xz
bfab31be4f95d8f8eec40f7e7dfe7caad494a1496945989a0b5936d24125a711 114 non-free/debian-installer/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-mips/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-mips/Packages.xz
37a9b6efa8e5b0f9ee74f3202db161c209fa066830268ea30b206d8b1f9052c1 114 non-free/debian-installer/binary-mips/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-mips64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-mips64el/Packages.xz
d50cbb62225425908c57af80d918ccc99859ba4803be62b44da8d8159eb86847 118 non-free/debian-installer/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-mipsel/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-mipsel/Packages.xz
a30e01cfad8b7f5e5f7115787590a2a3342ddea51857ccb230180e555b9c68a4 116 non-free/debian-installer/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-ppc64el/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-ppc64el/Packages.xz
33da686a19b40e79ba83a7daf8d863010c36ff9f6d063ecc698a91d3cf626f53 117 non-free/debian-installer/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-s390x/Packages
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-s390x/Packages.xz
acc91e4fa6121d24aba8b1e8f39cffde4512bb549044cbf88f998cd4dc0423df 115 non-free/debian-installer/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/i18n/Translation-en
d3dda84eb03b9738d118eb2be78e246106900493c0ae07819ad60815134a8058 14 non-free/i18n/Translation-en.bz2
75a93381931d1caa336f5674cd3cb576876aa87c13d0c66dd6be20046df7a1b1 116 non-free/source/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/source/Sources
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/source/Sources.xz
-----BEGIN PGP SIGNATURE-----
iQIzBAEBCAAdFiEEFukLP99l7eOqfzI8BO5yN7fUU+wFAmDa1L8ACgkQBO5yN7fU
U+xlnw/+N8x7oU6dO9Tofgv3aFmHVOOyzbO0a6o1xkweoSxixcZVebSEv0o0tOpg
FIHSc47OvfQGI4HrSNAwO3iyNHVqY0gxEaXr9kOeIC/4v8Vi8FYOsHI3TQ21ZUIS
ZBVPAHi5gB0TztO5MArDXiJuo6EY/g810gPsQrp3op3Xn6EhSB9Bi2Z/Bi/rP2Pk
D/vq+JqztcJ7ufsS0v17Z24gYdR4MZlTi/CLrQeuqkqcWdx0lLa/YX9GTMXK8YTm
j8O0dfJWqtgdq/0vN0zez9r49rkxszp8q7lF1+U4QvGxstUdMtZzSVVM0qnE2fys
4lb2J+Qamv+ycqflWccINQvVbHI8TruthzGOi7hn5OKDVE84Jd2etAczd8eKvAIa
eckMCMI0LzqbuMYWglVIUCFbUDNrc+UnLT022k+l3mkV9zpQEmkDRvPHZRXxPCaF
unETH3IsJ+g+HK8OskgJoFcV4efHRW3za20CNhvGExf/Yl0/UmQl2OO1DeK9JhBQ
LYGyxKNXSsutdN1iDVGmNl6xwuwN5JRf65LHrO4VSJ+wF3fVpri7xqrg7+irMccd
nC7UNT064HIREumseM0PqkFx3Rw0Ysiv/znsCW85YOyc3p8xBDlzBlJVgiAMZsmx
gkTOZRzHbZ34a4VI5vpN2Cf7qAAN+kuAtwsBE0905UnpLDIyYraJAjMEAQEIAB0W
IQQBRtxtSgspFL3tNNtkis/WIvPROAUCYNrU2gAKCRBkis/WIvPROMtID/4+d48X
MbZYDh8KSjz66qt/juhsTKeGQlRKxnbwxUKRRnhXrwd4MmHsUDqJQk3br4B8TqAp
8V7H8ZT7hhcvMD8ghj4Sw/dG8BPLXntDLxsSRnb5oMZxvwfeeH1smnTu6WOQUgzr
J3p9CEBiR8VBm3hy10QNPepkqhggPdZvGM6N8G7OBSteFwVCRsA6MW8A+SN24t9g
qp7898+iebhRCxhCXNiP6pMXI67dZtZbCWKSPivFC7IoCedSlOPmmtXgJZo3fhEt
vr09VKSyr7+w8IBpRZlhElq2WFHPm5TnK13iGmxhKR0msApj4RciVH/nFLLaRUHC
05qXmA/4vDgrlzIme14sTHiaEVHeFk4DYWxae69MbbppxrYhv488jHoeIcUxJlTw
ydi+TmmMwtpZdugYtAHKBxvHtyxiB5d4DHRykkwvCdv0OWPja6GDPDTMO1ix+86E
ruidHOr6g4maV4EvjqqXkE2MFYAHxVaMkSz2jPHtEKlneusLSdnYgghVCByf4xbl
FTho8AKy88F4qT0oDNsedrlv9OZe7eif4hi9r6NabVUcI+4wAKjMV40mg5EpXR/F
otGmeLMo4LO9kdnHuMhFNdlNJCOdBy2czJV/f7G3KprPXj4TSgDV3jIimwnGf4MQ
6OIWLDlKkCemDssg/bCLX8Z8GacZAWvI+YySPQ==
=dXdg
-----END PGP SIGNATURE-----

View File

@ -1,426 +0,0 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Origin: Debian
Label: Debian-Security
Suite: testing-security
Codename: bullseye-security
Date: Tue, 29 Jun 2021 11:21:24 UTC
Valid-Until: Tue, 06 Jul 2021 11:21:24 UTC
Acquire-By-Hash: yes
Architectures: amd64 arm64 armel armhf i386 mips64el mipsel ppc64el s390x
Components: updates/main updates/contrib updates/non-free
Description: Debian x.y Testing - Security Updates - Not Released
SHA256:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-source
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-source.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-all/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-all/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-all/Packages.xz
dad6f4ef22f149e992d811397605c205e6200f0f997310befb7b30281fb20529 130 contrib/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-amd64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-amd64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-amd64/Packages.xz
95155862fd275db390e032008b507c6de9f5c88b6675067a2213b733ff65a71b 132 contrib/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-arm64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-arm64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-arm64/Packages.xz
dc44f7c5477b840abd146ba41d3c1295a078cabdd0b8a5c3e8de1f8981bb4cab 132 contrib/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-armel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-armel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-armel/Packages.xz
518a30fe51451f86b09975c1d86fa8eaae5f946f016acdbf4c9c84629deb6323 132 contrib/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-armhf/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-armhf/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-armhf/Packages.xz
a786baa4947efa2435a6fe9aa388ac9e0b1f44f058ea032c3f5717eb8abb69b2 132 contrib/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-i386/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-i386/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-i386/Packages.xz
0c21f39be33458944fdc075f9a56a29101e606f36169a4a8e915768f5213f8c1 131 contrib/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-mips64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-mips64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-mips64el/Packages.xz
312b1b4faf7b4353c795af321e782b07c1cb0e06b1308e17f8a28b5309604616 135 contrib/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-mipsel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-mipsel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-mipsel/Packages.xz
770d42f09c2fe61ad419b63a36995e5824cb50d95244aa92aadcbe5b699443b6 133 contrib/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-ppc64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-ppc64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-ppc64el/Packages.xz
41441fd2370acf8ecc41cc58113534747d97f6484c8f02f28310113f302428ab 134 contrib/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-s390x/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/binary-s390x/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/binary-s390x/Packages.xz
991da76f3ef2b5ac24b81475e9de36464b139b435335cd98d073425bae2f7b59 132 contrib/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-all/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-all/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-all/Packages.xz
dad6f4ef22f149e992d811397605c205e6200f0f997310befb7b30281fb20529 130 contrib/debian-installer/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-amd64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-amd64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-amd64/Packages.xz
95155862fd275db390e032008b507c6de9f5c88b6675067a2213b733ff65a71b 132 contrib/debian-installer/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-arm64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-arm64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-arm64/Packages.xz
dc44f7c5477b840abd146ba41d3c1295a078cabdd0b8a5c3e8de1f8981bb4cab 132 contrib/debian-installer/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-armel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-armel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-armel/Packages.xz
518a30fe51451f86b09975c1d86fa8eaae5f946f016acdbf4c9c84629deb6323 132 contrib/debian-installer/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-armhf/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-armhf/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-armhf/Packages.xz
a786baa4947efa2435a6fe9aa388ac9e0b1f44f058ea032c3f5717eb8abb69b2 132 contrib/debian-installer/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-i386/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-i386/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-i386/Packages.xz
0c21f39be33458944fdc075f9a56a29101e606f36169a4a8e915768f5213f8c1 131 contrib/debian-installer/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-mips64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-mips64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-mips64el/Packages.xz
312b1b4faf7b4353c795af321e782b07c1cb0e06b1308e17f8a28b5309604616 135 contrib/debian-installer/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-mipsel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-mipsel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-mipsel/Packages.xz
770d42f09c2fe61ad419b63a36995e5824cb50d95244aa92aadcbe5b699443b6 133 contrib/debian-installer/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-ppc64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-ppc64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-ppc64el/Packages.xz
41441fd2370acf8ecc41cc58113534747d97f6484c8f02f28310113f302428ab 134 contrib/debian-installer/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-s390x/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-s390x/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-s390x/Packages.xz
991da76f3ef2b5ac24b81475e9de36464b139b435335cd98d073425bae2f7b59 132 contrib/debian-installer/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/i18n/Translation-en
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/i18n/Translation-en.xz
47fd677ef64107683fd0b8bac29352c4a047b2d7310462753ca47eeb3096ee3b 133 contrib/source/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/source/Sources
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/source/Sources.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/source/Sources.xz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-source
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-source.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/Contents-udeb-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/Contents-udeb-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-all/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/binary-all/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/binary-all/Packages.xz
0a4d9c1090736291f4a41265e239709235907edec6c725bedccfc41ce1e52a05 127 main/binary-all/Release
bbae2caedf9d88a82df30e5af6ec0eb493604d347fd98b7a9749568c304df22a 1928 main/binary-amd64/Packages
ac756c56a08c59f831db4d6c17d480b21f2704b3d51961fa2ff284a0a82a769a 763 main/binary-amd64/Packages.gz
16278569504f0663d817c45332e2034d8343107e1c445cde9994f6136b3fcb00 812 main/binary-amd64/Packages.xz
26279f9ed5f2d43aa257b16534a3e1f31e83c4d4148a6c6450b6f08152659a8c 129 main/binary-amd64/Release
89a8e73a35cc1ff8a08b9ca35e097823dadf2a9d31ae5375f5f7b7f2fe4e2b65 1928 main/binary-arm64/Packages
a8c3b768b8b335fdc885c55601c5132f4d3a917b2cfd687d2e89ea185866ee1d 762 main/binary-arm64/Packages.gz
3c2ce72e75afb59119f1e2f83516ab4d727ed74286fbdc799185eb0fb28e2e55 808 main/binary-arm64/Packages.xz
bd8c91fe3be0b96a19b100325c41dc9fd49623d2df10436c9db044a276fac392 129 main/binary-arm64/Release
19f9cc6b15e0c8e42af1b02c29419d3e863a3511400ec9d2a5d5c020df19abe4 1928 main/binary-armel/Packages
1b96bcd0d2e611afd97c3a7d46781c8ef2fdba6c1eb45aaf6e1d1787c637eb25 763 main/binary-armel/Packages.gz
960909e14220da768efa538f27cd2ae2bdbe3e17ea82ef03506b22f0fb8f4179 804 main/binary-armel/Packages.xz
2e8e8045b28d0ad3bdae7e86e6ba22c103a7775af0e7ae766e482b59b559371b 129 main/binary-armel/Release
d14cd548e7903c2a734afeea287bc8a6fc1e0c84b570b0e04af074e485e4cf02 1928 main/binary-armhf/Packages
99eeb65ade7992b28a12b558ccd3ef443aad85b36cf7cd21989993f016ad736e 764 main/binary-armhf/Packages.gz
ae2120dcd2dd3c6c616984dd206745f2b895e2e98968dea1c4a7e6041de9a644 808 main/binary-armhf/Packages.xz
3dcb7a44da260ad8a0d0ba7e25233f7c2b35722c565b6d435fd0d493b018d355 129 main/binary-armhf/Release
e51c53b7af69fed94ba0b0f947fead354e327f923b00263f919cc790a9426dd3 1922 main/binary-i386/Packages
4cfcd11b768856e7f1de9d3bd985880c8cac118d4e9128831032f1fe1e75c9b2 763 main/binary-i386/Packages.gz
6e863d5d686d7d35a77935bbcd92e3c31ddc2cbd97a50cdfa939d628f1080283 808 main/binary-i386/Packages.xz
e9fc3062aeec4399fab10a52f6e7e81a887ce6fec27efb40974621b13d973c9f 128 main/binary-i386/Release
3b08feaf135dc663036881aeed987dc3aba8652d740f276feba59eac8e293b3d 1946 main/binary-mips64el/Packages
dcce951cd1bef0b08fa7b464b37d805de6a9b59bce36472f68b77535e565f317 759 main/binary-mips64el/Packages.gz
9b909a22910a80c29dc6d80caa246af17413784049d600e415cd77543b06bfe9 812 main/binary-mips64el/Packages.xz
664edd8fcb5b7b5c163cbc0ad06cb93882b63c2ec67f41ac5b3b1c2c042e5a44 132 main/binary-mips64el/Release
d2823a214b46aa9cb76340cc4b0cc79e331f204ed5ff87a3161fa480743c219f 1934 main/binary-mipsel/Packages
f8c288471b2c6c2fc27100d4d380057f43fd5a8bd335e74be9e63e48244e951e 764 main/binary-mipsel/Packages.gz
118034c37ac931ec11e26a51e1c48014374181f64bf90b001ce661534ba0157d 808 main/binary-mipsel/Packages.xz
29fff1f8b8a24fb9558402acc8f62d81152d9acfaa5e93b84a743c1305c8480b 130 main/binary-mipsel/Release
b89f554d34172357ce4986fe329b9d76e409c8421cfcab9390e6e9c616b47ed6 1940 main/binary-ppc64el/Packages
800486b18b08eb73d535d6d36ef3e8782595765f4504ca8ce769b02e341df5d5 762 main/binary-ppc64el/Packages.gz
d9c8e0c73dd37d02a2159c0e120e620489b324bb5c18e9dae15364d26558fbb2 812 main/binary-ppc64el/Packages.xz
962bb7d7a66b74d348f41c770a8d67d45ac44ebaddebdaa221d9cd97650b47f3 131 main/binary-ppc64el/Release
25f871341e67f34847523c31d0bd302aa00eb394ef3b47c83153f3a3fb1bc2f1 1928 main/binary-s390x/Packages
4bb0ea6e9fcafc492bf50d5ea24783dbdae1924d5803c792f94373521e6dfc99 764 main/binary-s390x/Packages.gz
03d65434978fbf391da00927b0b587d1e5364cb254f03391efe02f29fffea462 808 main/binary-s390x/Packages.xz
9f779b6d069ad34c2d19613aef3d4292ed4bde866ceef7dd2c54caa1f61859a1 129 main/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-all/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-all/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-all/Packages.xz
0a4d9c1090736291f4a41265e239709235907edec6c725bedccfc41ce1e52a05 127 main/debian-installer/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-amd64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-amd64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-amd64/Packages.xz
26279f9ed5f2d43aa257b16534a3e1f31e83c4d4148a6c6450b6f08152659a8c 129 main/debian-installer/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-arm64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-arm64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-arm64/Packages.xz
bd8c91fe3be0b96a19b100325c41dc9fd49623d2df10436c9db044a276fac392 129 main/debian-installer/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-armel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-armel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-armel/Packages.xz
2e8e8045b28d0ad3bdae7e86e6ba22c103a7775af0e7ae766e482b59b559371b 129 main/debian-installer/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-armhf/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-armhf/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-armhf/Packages.xz
3dcb7a44da260ad8a0d0ba7e25233f7c2b35722c565b6d435fd0d493b018d355 129 main/debian-installer/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-i386/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-i386/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-i386/Packages.xz
e9fc3062aeec4399fab10a52f6e7e81a887ce6fec27efb40974621b13d973c9f 128 main/debian-installer/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-mips64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-mips64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-mips64el/Packages.xz
664edd8fcb5b7b5c163cbc0ad06cb93882b63c2ec67f41ac5b3b1c2c042e5a44 132 main/debian-installer/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-mipsel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-mipsel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-mipsel/Packages.xz
29fff1f8b8a24fb9558402acc8f62d81152d9acfaa5e93b84a743c1305c8480b 130 main/debian-installer/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-ppc64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-ppc64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-ppc64el/Packages.xz
962bb7d7a66b74d348f41c770a8d67d45ac44ebaddebdaa221d9cd97650b47f3 131 main/debian-installer/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-s390x/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 main/debian-installer/binary-s390x/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 main/debian-installer/binary-s390x/Packages.xz
9f779b6d069ad34c2d19613aef3d4292ed4bde866ceef7dd2c54caa1f61859a1 129 main/debian-installer/binary-s390x/Release
b54ef4560b294e63b9b4552e1e0828060c8c5038db75d25a9f8bdd1414205feb 1190 main/i18n/Translation-en
05b17d5fe2084e0d21081a714c09751aa17c147d01e3148bd42d48643e54f3e9 596 main/i18n/Translation-en.xz
545c498daba5eefe133c68836f2086ebbc6027e496a09af436609e0e0f3130c0 130 main/source/Release
194afb538a6393cbc3515804a8577b6d9f939e4dfa28b2b56b5b24c68799e299 1210 main/source/Sources
2253816c6e56c03e9dd8b7d11780ea40c2959c81327264257d60938858e7c62d 727 main/source/Sources.gz
82bff31f0c30a1c0cb5c5691430a19a52ec855d69c101d7fb927e7fc38b5e9b3 780 main/source/Sources.xz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-source
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-source.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-amd64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-amd64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-arm64
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-arm64.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-armel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-armel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-armhf
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-armhf.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-i386
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-i386.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-mips
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-mips.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-mips64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-mips64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-mipsel
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-mipsel.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-ppc64el
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-ppc64el.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-s390x
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-s390x.gz
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-all/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-all/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-all/Packages.xz
09692a805a856602cecf048c99e3371a98d391bbde03f6f3b1b348fd14988f57 131 non-free/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-amd64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-amd64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-amd64/Packages.xz
62f830e8bbe3cdae15bd5eb6a49a7eab262e3b4f1ea05dd15334d150e44d2067 133 non-free/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-arm64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-arm64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-arm64/Packages.xz
d9bfd44bcf537cce90d40ea23f58b87c5b9a648bf72fdeaeb1d15cd797db1575 133 non-free/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-armel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-armel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-armel/Packages.xz
7bf07a333833bea75d4279a96627a4da49631dc3b81f486fcd2ead096fa96831 133 non-free/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-armhf/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-armhf/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-armhf/Packages.xz
4cddc7a3787e70376316186145565b1de2bbf50ade4f5183eeab85d2c7069b83 133 non-free/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-i386/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-i386/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-i386/Packages.xz
149101f480b7ab75f59882ea752607f27fac96be5b8696e7d467ac4fb6f459c3 132 non-free/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-mips64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-mips64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-mips64el/Packages.xz
4eb4c5bc3f0b35263adef552c3e843e678c102af0bee91b90d8958516f8592c6 136 non-free/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-mipsel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-mipsel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-mipsel/Packages.xz
a9c9896fa7a441c2554b9b369b8bb8f5fdf2c9098d60b5a49b5208314ec076a0 134 non-free/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-ppc64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-ppc64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-ppc64el/Packages.xz
a175762edf42deda74e5e1944cb613c84c769916b8186d71939b784de861edf6 135 non-free/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/binary-s390x/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/binary-s390x/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/binary-s390x/Packages.xz
ecc1d55a4e9884e2ec3cebc0fa7e774e220c388406390e9b6c87e0884d627121 133 non-free/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-all/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-all/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-all/Packages.xz
09692a805a856602cecf048c99e3371a98d391bbde03f6f3b1b348fd14988f57 131 non-free/debian-installer/binary-all/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-amd64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-amd64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-amd64/Packages.xz
62f830e8bbe3cdae15bd5eb6a49a7eab262e3b4f1ea05dd15334d150e44d2067 133 non-free/debian-installer/binary-amd64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-arm64/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-arm64/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-arm64/Packages.xz
d9bfd44bcf537cce90d40ea23f58b87c5b9a648bf72fdeaeb1d15cd797db1575 133 non-free/debian-installer/binary-arm64/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-armel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-armel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-armel/Packages.xz
7bf07a333833bea75d4279a96627a4da49631dc3b81f486fcd2ead096fa96831 133 non-free/debian-installer/binary-armel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-armhf/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-armhf/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-armhf/Packages.xz
4cddc7a3787e70376316186145565b1de2bbf50ade4f5183eeab85d2c7069b83 133 non-free/debian-installer/binary-armhf/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-i386/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-i386/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-i386/Packages.xz
149101f480b7ab75f59882ea752607f27fac96be5b8696e7d467ac4fb6f459c3 132 non-free/debian-installer/binary-i386/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-mips64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-mips64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-mips64el/Packages.xz
4eb4c5bc3f0b35263adef552c3e843e678c102af0bee91b90d8958516f8592c6 136 non-free/debian-installer/binary-mips64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-mipsel/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-mipsel/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-mipsel/Packages.xz
a9c9896fa7a441c2554b9b369b8bb8f5fdf2c9098d60b5a49b5208314ec076a0 134 non-free/debian-installer/binary-mipsel/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-ppc64el/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-ppc64el/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-ppc64el/Packages.xz
a175762edf42deda74e5e1944cb613c84c769916b8186d71939b784de861edf6 135 non-free/debian-installer/binary-ppc64el/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-s390x/Packages
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-s390x/Packages.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-s390x/Packages.xz
ecc1d55a4e9884e2ec3cebc0fa7e774e220c388406390e9b6c87e0884d627121 133 non-free/debian-installer/binary-s390x/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/i18n/Translation-en
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/i18n/Translation-en.xz
16dd4854d2bfc851746dec540a6f40c65db70dd5f8dcbeb2450182ce0fa3e306 134 non-free/source/Release
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/source/Sources
f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/source/Sources.gz
0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/source/Sources.xz
-----BEGIN PGP SIGNATURE-----
iQIzBAEBCAAdFiEEN5SD2LYBYLFVs3Ldqo6BtDMff1AFAmDbAjQACgkQqo6BtDMf
f1AeSA//efWHS9SsdL69FqFvfFA4TBI5HRcWRnRDiaVrq4c1FpfUjV+FwgCzFjql
y5MqjOfhI2bnn9T8DHLiK1NY3q6H6RVTpjzD0YdVZ7i8Eobp+9YOkjgQ+fhVqDpS
Xuka5kw9rYbkZgONIHNpsQwgnPupfG8O5OTHPDCwO49hPfa8g0Ar9kwYbixguVRX
qBYXV/EOyx6TXM5u69nqnbIzgdOhTHipJYVi9AiEyiO+lwAbzvogaSn574EWDb5y
9pruXxqHYFea4uGShJtambZMeC528CQALbkZpHzeFCdlBtapSH9Lxmye3ciqC9kT
7Ns09+G/C/3kTWVBfA7rVztJ5Bw4PWPZafe/YceenuK0EipsOa94PpmKeDkWzRL6
1obFDHWpDXqEJIomkq1+BkflWq8mQiI97v36NpIBXKew/f+jWw+ydoIZMESdWM2H
LGXF4fM3tCLGOYVqz6sAfUv7jfemJuDQq6ZNDVvfVIwwdA/p+oeTdu5NaYcOEGRQ
nXxS4MLWox6kc9UUWpE72NHHm5Anq62L/F7JVKbl7pw9tw0C/pvgF/6ZlnloOYZv
g3JBpZd7bt3UCDMU8MoEKs/ZDVk3x6Q/k9H/+S6B3M98UAjRQI0p2cgXQqvWuYdL
E0mXb4vKcS5wE/qyeFq7hSB1MP6q4x5kcKZe8333Odj4K4BDPCGJAjMEAQEIAB0W
IQRSN87u8hLz1Rx0q+ARJpWg5WKzKgUCYNsCNAAKCRARJpWg5WKzKp93EACBl90s
J9hX0OjJbMlIlDR+bBYaD9k6EjONtZUKGMVGWWQkpuQKlHZqEEjcQaU1kIpmyiEv
C0R44im2ULdb4jmuyMKmt3KdGMMCdD1yIaaLEoY/7GE9ZANojIWLsDdhgCADW4o5
WPruSxtpBKxKmyANw4EkbHUTyNtZ109cPdyX+isqElD47JNx+yypf1JOUdgW75xF
P96EKOFmQJahhAz2cCAjgBjy5awjGiz7AFtS13sBhbd7rn6CwTTdZ7JMxkh+UP/5
Fl93hmIRe3b1c12woB960BS2ic2dLW7nCIUId8LSl13xGc3y2qY7jd+XKVCbsuaZ
6R67dMGuMi2wmvu2aKbzuopVgSRJHpjZWiR5InOTesRczRw4UPrlfUKmuElxeGTi
BZxC1QtdB8ioRWmU7ArHO90v6zkxfXOhLCs5wOdkMB6kDB95CzmEjU985w3H5Hiz
GRAxThFpkcAgn96IFeIFvXk8xRLzPTyr3Xdm5QGiX6rZOYcM7BeH7+wyuboXsz4z
KCaTE53e1ANJSQOlxzozrHuPQ9c05lD4tos+gwBOm15y6Jwdf8+uCM7dwFhHjp50
65+QMfdcqkTGcRY3fcJsS3Yfu9gluQ916YBuUU03PNUDeaC6XPtbTVMLMsbQshWu
za+2eAEWLuyd9bvQ2VKilIJu7bozK8E5Y9UPng==
=Bno8
-----END PGP SIGNATURE-----

View File

@ -1,422 +0,0 @@
use std::path::PathBuf;
use anyhow::{bail, format_err, Error};
use proxmox_apt::config::APTConfig;
use proxmox_apt::repositories::{
check_repositories, get_current_release_codename, standard_repositories, APTRepositoryFile,
APTRepositoryHandle, APTRepositoryInfo, APTStandardRepository, DebianCodename,
};
#[test]
fn test_parse_write() -> Result<(), Error> {
test_parse_write_dir("sources.list.d")?;
test_parse_write_dir("sources.list.d.expected")?; // check if it's idempotent
Ok(())
}
fn test_parse_write_dir(read_dir: &str) -> Result<(), Error> {
let test_dir = std::env::current_dir()?.join("tests");
let read_dir = test_dir.join(read_dir);
let write_dir = test_dir.join("sources.list.d.actual");
let expected_dir = test_dir.join("sources.list.d.expected");
if write_dir.is_dir() {
std::fs::remove_dir_all(&write_dir)
.map_err(|err| format_err!("unable to remove dir {:?} - {}", write_dir, err))?;
}
std::fs::create_dir_all(&write_dir)
.map_err(|err| format_err!("unable to create dir {:?} - {}", write_dir, err))?;
let mut files = vec![];
let mut errors = vec![];
for entry in std::fs::read_dir(read_dir)? {
let path = entry?.path();
match APTRepositoryFile::new(&path)? {
Some(mut file) => match file.parse() {
Ok(()) => files.push(file),
Err(err) => errors.push(err),
},
None => bail!("unexpected None for '{:?}'", path),
}
}
assert!(errors.is_empty());
for file in files.iter_mut() {
let path = match &file.path {
Some(path) => path,
None => continue,
};
let path = PathBuf::from(path);
let new_path = write_dir.join(path.file_name().unwrap());
file.path = Some(new_path.into_os_string().into_string().unwrap());
file.digest = None;
file.write()?;
}
let mut expected_count = 0;
for entry in std::fs::read_dir(expected_dir)? {
expected_count += 1;
let expected_path = entry?.path();
let actual_path = write_dir.join(expected_path.file_name().unwrap());
let expected_contents = std::fs::read(&expected_path)
.map_err(|err| format_err!("unable to read {:?} - {}", expected_path, err))?;
let actual_contents = std::fs::read(&actual_path)
.map_err(|err| format_err!("unable to read {:?} - {}", actual_path, err))?;
assert_eq!(
expected_contents, actual_contents,
"Use\n\ndiff {:?} {:?}\n\nif you're not fluent in byte decimals",
expected_path, actual_path
);
}
let actual_count = std::fs::read_dir(write_dir)?.count();
assert_eq!(expected_count, actual_count);
Ok(())
}
#[test]
fn test_digest() -> Result<(), Error> {
let test_dir = std::env::current_dir()?.join("tests");
let read_dir = test_dir.join("sources.list.d");
let write_dir = test_dir.join("sources.list.d.digest");
if write_dir.is_dir() {
std::fs::remove_dir_all(&write_dir)
.map_err(|err| format_err!("unable to remove dir {:?} - {}", write_dir, err))?;
}
std::fs::create_dir_all(&write_dir)
.map_err(|err| format_err!("unable to create dir {:?} - {}", write_dir, err))?;
let path = read_dir.join("standard.list");
let mut file = APTRepositoryFile::new(&path)?.unwrap();
file.parse()?;
let new_path = write_dir.join(path.file_name().unwrap());
file.path = Some(new_path.clone().into_os_string().into_string().unwrap());
let old_digest = file.digest.unwrap();
// file does not exist yet...
assert!(file.read_with_digest().is_err());
assert!(file.write().is_err());
// ...but it should work if there's no digest
file.digest = None;
file.write()?;
// overwrite with old contents...
std::fs::copy(path, new_path)?;
// modify the repo
let mut repo = file.repositories.first_mut().unwrap();
repo.enabled = !repo.enabled;
// ...then it should work
file.digest = Some(old_digest);
file.write()?;
// expect a different digest, because the repo was modified
let (_, new_digest) = file.read_with_digest()?;
assert_ne!(old_digest, new_digest);
assert!(file.write().is_err());
Ok(())
}
#[test]
fn test_empty_write() -> Result<(), Error> {
let test_dir = std::env::current_dir()?.join("tests");
let read_dir = test_dir.join("sources.list.d");
let write_dir = test_dir.join("sources.list.d.remove");
if write_dir.is_dir() {
std::fs::remove_dir_all(&write_dir)
.map_err(|err| format_err!("unable to remove dir {:?} - {}", write_dir, err))?;
}
std::fs::create_dir_all(&write_dir)
.map_err(|err| format_err!("unable to create dir {:?} - {}", write_dir, err))?;
let path = read_dir.join("standard.list");
let mut file = APTRepositoryFile::new(&path)?.unwrap();
file.parse()?;
let new_path = write_dir.join(path.file_name().unwrap());
file.path = Some(new_path.into_os_string().into_string().unwrap());
file.digest = None;
file.write()?;
assert!(file.exists());
file.repositories.clear();
file.write()?;
assert!(!file.exists());
Ok(())
}
#[test]
fn test_check_repositories() -> Result<(), Error> {
let test_dir = std::env::current_dir()?.join("tests");
let read_dir = test_dir.join("sources.list.d");
proxmox_apt::config::init(APTConfig::new(
Some(&test_dir.into_os_string().into_string().unwrap()),
None,
));
let absolute_suite_list = read_dir.join("absolute_suite.list");
let mut file = APTRepositoryFile::new(&absolute_suite_list)?.unwrap();
file.parse()?;
let infos = check_repositories(&[file], DebianCodename::Bullseye);
assert!(infos.is_empty());
let pve_list = read_dir.join("pve.list");
let mut file = APTRepositoryFile::new(&pve_list)?.unwrap();
file.parse()?;
let path_string = pve_list.into_os_string().into_string().unwrap();
let origins = [
"Debian", "Debian", "Proxmox", "Proxmox", "Proxmox", "Debian",
];
let mut expected_infos = vec![];
for (n, origin) in origins.into_iter().enumerate() {
expected_infos.push(APTRepositoryInfo {
path: path_string.clone(),
index: n,
property: None,
kind: "origin".to_string(),
message: origin.to_string(),
});
}
expected_infos.sort();
let mut infos = check_repositories(&[file], DebianCodename::Bullseye);
infos.sort();
assert_eq!(infos, expected_infos);
let bad_sources = read_dir.join("bad.sources");
let mut file = APTRepositoryFile::new(&bad_sources)?.unwrap();
file.parse()?;
let path_string = bad_sources.into_os_string().into_string().unwrap();
let mut expected_infos = vec![
APTRepositoryInfo {
path: path_string.clone(),
index: 0,
property: Some("Suites".to_string()),
kind: "warning".to_string(),
message: "suite 'sid' should not be used in production!".to_string(),
},
APTRepositoryInfo {
path: path_string.clone(),
index: 1,
property: Some("Suites".to_string()),
kind: "warning".to_string(),
message: "old suite 'lenny' configured!".to_string(),
},
APTRepositoryInfo {
path: path_string.clone(),
index: 2,
property: Some("Suites".to_string()),
kind: "warning".to_string(),
message: "old suite 'stretch' configured!".to_string(),
},
APTRepositoryInfo {
path: path_string.clone(),
index: 3,
property: Some("Suites".to_string()),
kind: "warning".to_string(),
message: "use the name of the stable distribution instead of 'stable'!".to_string(),
},
APTRepositoryInfo {
path: path_string.clone(),
index: 4,
property: Some("Suites".to_string()),
kind: "ignore-pre-upgrade-warning".to_string(),
message: "suite 'bookworm' should not be used in production!".to_string(),
},
APTRepositoryInfo {
path: path_string.clone(),
index: 5,
property: Some("Suites".to_string()),
kind: "warning".to_string(),
message: "suite 'testing' should not be used in production!".to_string(),
},
];
for n in 0..=5 {
expected_infos.push(APTRepositoryInfo {
path: path_string.clone(),
index: n,
property: None,
kind: "origin".to_string(),
message: "Debian".to_string(),
});
}
expected_infos.sort();
let mut infos = check_repositories(&[file], DebianCodename::Bullseye);
infos.sort();
assert_eq!(infos, expected_infos);
let bad_security = read_dir.join("bad-security.list");
let mut file = APTRepositoryFile::new(&bad_security)?.unwrap();
file.parse()?;
let path_string = bad_security.into_os_string().into_string().unwrap();
let mut expected_infos = vec![];
for n in 0..=1 {
expected_infos.push(APTRepositoryInfo {
path: path_string.clone(),
index: n,
property: Some("Suites".to_string()),
kind: "warning".to_string(),
message: "expected suite 'bullseye-security'".to_string(),
});
}
for n in 0..=1 {
expected_infos.push(APTRepositoryInfo {
path: path_string.clone(),
index: n,
property: None,
kind: "origin".to_string(),
message: "Debian".to_string(),
});
}
expected_infos.sort();
let mut infos = check_repositories(&[file], DebianCodename::Bullseye);
infos.sort();
assert_eq!(infos, expected_infos);
Ok(())
}
#[test]
fn test_get_cached_origin() -> Result<(), Error> {
let test_dir = std::env::current_dir()?.join("tests");
let read_dir = test_dir.join("sources.list.d");
proxmox_apt::config::init(APTConfig::new(
Some(&test_dir.into_os_string().into_string().unwrap()),
None,
));
let pve_list = read_dir.join("pve.list");
let mut file = APTRepositoryFile::new(&pve_list)?.unwrap();
file.parse()?;
let origins = [
Some("Debian".to_string()),
Some("Debian".to_string()),
Some("Proxmox".to_string()),
None, // no cache file exists
None, // no cache file exists
Some("Debian".to_string()),
];
assert_eq!(file.repositories.len(), origins.len());
for (n, repo) in file.repositories.iter().enumerate() {
assert_eq!(repo.get_cached_origin()?, origins[n]);
}
Ok(())
}
#[test]
fn test_standard_repositories() -> Result<(), Error> {
let test_dir = std::env::current_dir()?.join("tests");
let read_dir = test_dir.join("sources.list.d");
let mut expected = vec![
APTStandardRepository::from(APTRepositoryHandle::Enterprise),
APTStandardRepository::from(APTRepositoryHandle::NoSubscription),
APTStandardRepository::from(APTRepositoryHandle::Test),
APTStandardRepository::from(APTRepositoryHandle::CephQuincy),
APTStandardRepository::from(APTRepositoryHandle::CephQuincyTest),
APTStandardRepository::from(APTRepositoryHandle::CephPacific),
APTStandardRepository::from(APTRepositoryHandle::CephPacificTest),
APTStandardRepository::from(APTRepositoryHandle::CephOctopus),
APTStandardRepository::from(APTRepositoryHandle::CephOctopusTest),
];
let absolute_suite_list = read_dir.join("absolute_suite.list");
let mut file = APTRepositoryFile::new(&absolute_suite_list)?.unwrap();
file.parse()?;
let std_repos = standard_repositories(&[file], "pve", DebianCodename::Bullseye);
assert_eq!(std_repos, expected);
let pve_list = read_dir.join("pve.list");
let mut file = APTRepositoryFile::new(&pve_list)?.unwrap();
file.parse()?;
let file_vec = vec![file];
let std_repos = standard_repositories(&file_vec, "pbs", DebianCodename::Bullseye);
assert_eq!(&std_repos, &expected[0..=2]);
expected[0].status = Some(false);
expected[1].status = Some(true);
let std_repos = standard_repositories(&file_vec, "pve", DebianCodename::Bullseye);
assert_eq!(std_repos, expected);
let pve_alt_list = read_dir.join("pve-alt.list");
let mut file = APTRepositoryFile::new(&pve_alt_list)?.unwrap();
file.parse()?;
let file_vec = vec![file];
expected[0].status = Some(true);
expected[1].status = Some(true);
expected[2].status = Some(false);
let std_repos = standard_repositories(&file_vec, "pve", DebianCodename::Bullseye);
assert_eq!(std_repos, expected);
Ok(())
}
#[test]
fn test_get_current_release_codename() -> Result<(), Error> {
let codename = get_current_release_codename()?;
assert!(codename == DebianCodename::Bullseye);
Ok(())
}

View File

@ -1,5 +0,0 @@
# From Debian Administrator's Handbook
deb http://packages.falcot.com/ updates/
deb http://user.name@packages.falcot.com:80/ internal/

View File

@ -1,5 +0,0 @@
# From Debian Administrator's Handbook
Types: deb
URIs: http://packages.falcot.com/
Suites: updates/ internal/

View File

@ -1,4 +0,0 @@
deb http://security.debian.org/debian-security/ bullseye/updates main contrib
deb https://security.debian.org bullseye/updates main contrib

View File

@ -1,30 +0,0 @@
Types: deb
URIs: http://ftp.at.debian.org/debian
Suites: sid
Components: main contrib
Types: deb
URIs: http://ftp.at.debian.org/debian
Suites: lenny-backports
Components: contrib
Types: deb
URIs: http://security.debian.org:80
Suites: stretch/updates
Components: main contrib
Types: deb
URIs: http://ftp.at.debian.org:80/debian
Suites: stable
Components: main
Types: deb
URIs: http://ftp.at.debian.org/debian
Suites: bookworm
Components: main
Types: deb
URIs: http://ftp.at.debian.org/debian
Suites: testing
Components: main

View File

@ -1,16 +0,0 @@
# comment in here
Types: deb deb-src
URIs: http://ftp.at.debian.org/debian
Suites: bullseye-updates
Components: main contrib
languages: it de fr
Enabled: false
languages-Add: ja
languages-Remove: de
# comment in here
Types: deb deb-src
URIs: http://ftp.at.debian.org/debian
Suites: bullseye
Components: main contrib

View File

@ -1,10 +0,0 @@
# deb [ trusted=yes ] cdrom:[Proxmox VE 5.1]/ stretch pve
# deb [ trusted=yes ] cdrom:[Proxmox VE 5.1]/proxmox/packages/ /
deb [ trusted=yes ] cdrom:[Proxmox VE 7.0 BETA]/ bullseye pve
deb cdrom:[Proxmox VE 7.0 BETA]/proxmox/packages/ /
deb [ trusted=yes ] cdrom:[Debian GNU/Linux 10.6.0 _Buster_ - Official amd64 NETINST 20200926-10:16]/ buster main

View File

@ -1,4 +0,0 @@
deb [ trusted=yes ] "file:///some/spacey/mount point/" bullseye pve
deb [ lang=it ] "file:///some/spacey/mount point/proxmox/packages/" /

View File

@ -1,10 +0,0 @@
# comment in here
Types: deb deb-src
URIs: http://ftp.at.debian.org/debian
Suites: bullseye bullseye-updates
Components: main contrib
Languages: it de fr
Enabled: false
Languages-Add: ja
Languages-Remove: de

View File

@ -1,6 +0,0 @@
# comment
deb [ lang=it,de arch=amd64 ] http://ftp.at.debian.org/debian bullseye main contrib
# non-free :(
deb [ lang=it,de arch=amd64 lang+=fr lang-=de ] http://ftp.at.debian.org/debian bullseye non-free

View File

@ -1,2 +0,0 @@
deb https://enterprise.proxmox.com/debian/pbs bullseye pbs-enterprise

View File

@ -1,8 +0,0 @@
deb https://enterprise.proxmox.com/debian bullseye pve-enterprise
deb http://download.proxmox.com/debian/ bullseye pve-no-subscription
# deb http://download.proxmox.com/debian bullseye pvetest
deb-src http://download.proxmox.com/debian bullseye pvetest

View File

@ -1,15 +0,0 @@
deb http://ftp.debian.org/debian bullseye main contrib
deb http://ftp.debian.org/debian bullseye-updates main contrib
# PVE pve-no-subscription repository provided by proxmox.com,
# NOT recommended for production use
deb http://download.proxmox.com/debian/pve bullseye pve-no-subscription
# deb https://enterprise.proxmox.com/debian/pve bullseye pve-enterprise
deb-src https://enterprise.proxmox.com/debian/pve buster pve-enterprise
# security updates
deb http://security.debian.org/debian-security bullseye-security main contrib

View File

@ -1,7 +0,0 @@
deb http://ftp.at.debian.org/debian bullseye main contrib
deb http://ftp.at.debian.org/debian bullseye-updates main contrib
# security updates
deb http://security.debian.org bullseye-security main contrib

View File

@ -1,11 +0,0 @@
Types: deb
URIs: http://ftp.at.debian.org/debian
Suites: bullseye bullseye-updates
Components: main contrib
# security updates
Types: deb
URIs: http://security.debian.org
Suites: bullseye-security
Components: main contrib

View File

@ -1,4 +0,0 @@
# From Debian Administrator's Handbook
deb http://packages.falcot.com/ updates/
deb http://user.name@packages.falcot.com:80/ internal/

View File

@ -1,5 +0,0 @@
# From Debian Administrator's Handbook
Types: deb
URIs: http://packages.falcot.com/
Suites: updates/ internal/

View File

@ -1,4 +0,0 @@
deb http://security.debian.org/debian-security/ bullseye/updates main contrib
deb https://security.debian.org bullseye/updates main contrib

View File

@ -1,29 +0,0 @@
Types: deb
URIs: http://ftp.at.debian.org/debian
Suites: sid
Components: main contrib
Types: deb
URIs: http://ftp.at.debian.org/debian
Suites: lenny-backports
Components: contrib
Types: deb
URIs: http://security.debian.org:80
Suites: stretch/updates
Components: main contrib
Suites: stable
URIs: http://ftp.at.debian.org:80/debian
Components: main
Types: deb
Suites: bookworm
URIs: http://ftp.at.debian.org/debian
Components: main
Types: deb
Suites: testing
URIs: http://ftp.at.debian.org/debian
Components: main
Types: deb

View File

@ -1,17 +0,0 @@
tYpeS: deb deb-src
uRis: http://ftp.at.debian.org/debian
suiTes: bullseye-updates
# comment in here
CompOnentS: main contrib
languages: it
de
fr
Enabled: off
languages-Add: ja
languages-Remove: de
types: deb deb-src
Uris: http://ftp.at.debian.org/debian
suites: bullseye
# comment in here
components: main contrib

View File

@ -1,7 +0,0 @@
#deb [trusted=yes] cdrom:[Proxmox VE 5.1]/ stretch pve
#deb [trusted=yes] cdrom:[Proxmox VE 5.1]/proxmox/packages/ /
deb [trusted=yes] cdrom:[Proxmox VE 7.0 BETA]/ bullseye pve
deb cdrom:[Proxmox VE 7.0 BETA]/proxmox/packages/ /
deb [ "trusted=yes" ] cdrom:[Debian GNU/Linux 10.6.0 _Buster_ - Official amd64 NETINST 20200926-10:16]/ buster main

View File

@ -1,2 +0,0 @@
deb [trusted=yes] "file:///some/spacey/mount point/" bullseye pve
deb [lang="it"] file:///some/spacey/"mount point"/proxmox/packages/ /

View File

@ -1,11 +0,0 @@
Types: deb deb-src
URIs: http://ftp.at.debian.org/debian
Suites: bullseye bullseye-updates
# comment in here
Components: main contrib
Languages: it
de
fr
Enabled: off
Languages-Add: ja
Languages-Remove: de

View File

@ -1,3 +0,0 @@
deb [ lang=it,de arch=amd64 ] http://ftp.at.debian.org/debian bullseye main contrib # comment
deb [ lang=it,de arch=amd64 lang+=fr lang-=de ] http://ftp.at.debian.org/debian bullseye non-free # non-free :(

View File

@ -1 +0,0 @@
deb https://enterprise.proxmox.com/debian/pbs bullseye pbs-enterprise

View File

@ -1,6 +0,0 @@
deb https://enterprise.proxmox.com/debian bullseye pve-enterprise
deb http://download.proxmox.com/debian/ bullseye pve-no-subscription
# deb http://download.proxmox.com/debian bullseye pvetest
deb-src http://download.proxmox.com/debian bullseye pvetest

View File

@ -1,12 +0,0 @@
deb http://ftp.debian.org/debian bullseye main contrib
deb http://ftp.debian.org/debian bullseye-updates main contrib
# PVE pve-no-subscription repository provided by proxmox.com,
# NOT recommended for production use
deb http://download.proxmox.com/debian/pve bullseye pve-no-subscription
# deb https://enterprise.proxmox.com/debian/pve bullseye pve-enterprise
deb-src https://enterprise.proxmox.com/debian/pve buster pve-enterprise
# security updates
deb http://security.debian.org/debian-security bullseye-security main contrib

View File

@ -1,6 +0,0 @@
deb http://ftp.at.debian.org/debian bullseye main contrib
deb http://ftp.at.debian.org/debian bullseye-updates main contrib
# security updates
deb http://security.debian.org bullseye-security main contrib

View File

@ -1,10 +0,0 @@
Types: deb
URIs: http://ftp.at.debian.org/debian
Suites: bullseye bullseye-updates
Components: main contrib
# security updates
Types: deb
URIs: http://security.debian.org
Suites: bullseye-security
Components: main contrib

View File

@ -1,23 +0,0 @@
[package]
name = "proxmox-async"
version = "0.4.1"
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
description = "Proxmox async/tokio helpers"
exclude.workspace = true
[dependencies]
anyhow.workspace = true
futures.workspace = true
lazy_static.workspace = true
pin-utils.workspace = true
tokio = { workspace = true, features = [ "net", "rt", "rt-multi-thread", "sync"] }
proxmox-io = { workspace = true, features = [ "tokio" ] }
proxmox-lang.workspace = true
[dev-dependencies]
tokio = { workspace = true, features = [ "macros" ] }

View File

@ -1,75 +0,0 @@
rust-proxmox-async (0.4.1) unstable; urgency=medium
* add SenderWriter
-- Proxmox Support Team <support@proxmox.com> Tue, 12 Apr 2022 14:21:53 +0200
rust-proxmox-async (0.4.0) unstable; urgency=medium
* use io error macros from proxmox-lang 1.1 instead of proxmox-sys
* drop compression code (moved to proxmox-compression)
-- Proxmox Support Team <support@proxmox.com> Mon, 21 Feb 2022 14:17:39 +0100
rust-proxmox-async (0.3.3) unstable; urgency=medium
* add net::udp::connect() helper
-- Proxmox Support Team <support@proxmox.com> Wed, 02 Feb 2022 12:57:41 +0100
rust-proxmox-async (0.3.2) unstable; urgency=medium
* replace RawWaker with the Wake trait from std, fixes a refcount leak
-- Proxmox Support Team <support@proxmox.com> Thu, 20 Jan 2022 10:08:25 +0100
rust-proxmox-async (0.3.1) unstable; urgency=medium
* fix #3618: proxmox-async: zip: add conditional EFS flag to zip files
-- Proxmox Support Team <support@proxmox.com> Wed, 12 Jan 2022 15:46:48 +0100
rust-proxmox-async (0.3.0) unstable; urgency=medium
* rebuild using proxmox-sys 0.2.0
-- Proxmox Support Team <support@proxmox.com> Tue, 23 Nov 2021 12:17:49 +0100
rust-proxmox-async (0.2.0) stable; urgency=medium
* improve dev docs
* move AsyncChannelWriter to src/io
* move TokioWriterAdapter to blocking
* remove duplicate src/stream/wrapped_reader_stream.rs
* split stream.rs into separate files
* split blocking.rs into separate files
* add copyright file
-- Proxmox Support Team <support@proxmox.com> Sat, 20 Nov 2021 16:54:58 +0100
rust-proxmox-async (0.1.0) stable; urgency=medium
* imported pbs-tools/src/zip.rs
* imported pbs-tools/src/compression.rs
* imported pbs-tools/src/tokio/tokio_writer_adapter.rs
* imported pbs-tools/src/stream.rs
* imported pbs-tools/src/broadcast_future.rs
* imported pbs-tools/src/blocking.rs
* imported pbs-runtime/src/lib.rs to runtime.rs
* initial release
-- Proxmox Support Team <support@proxmox.com> Fri, 19 Nov 2021 15:43:44 +0100

View File

@ -1,59 +0,0 @@
Source: rust-proxmox-async
Section: rust
Priority: optional
Build-Depends: debhelper (>= 12),
dh-cargo (>= 25),
cargo:native <!nocheck>,
rustc:native <!nocheck>,
libstd-rust-dev <!nocheck>,
librust-anyhow-1+default-dev <!nocheck>,
librust-futures-0.3+default-dev <!nocheck>,
librust-lazy-static-1+default-dev (>= 1.4-~~) <!nocheck>,
librust-pin-utils-0.1+default-dev <!nocheck>,
librust-proxmox-io-1+default-dev <!nocheck>,
librust-proxmox-io-1+tokio-dev <!nocheck>,
librust-proxmox-lang-1+default-dev (>= 1.1-~~) <!nocheck>,
librust-tokio-1+default-dev (>= 1.6-~~) <!nocheck>,
librust-tokio-1+net-dev (>= 1.6-~~) <!nocheck>,
librust-tokio-1+rt-dev (>= 1.6-~~) <!nocheck>,
librust-tokio-1+rt-multi-thread-dev (>= 1.6-~~) <!nocheck>,
librust-tokio-1+sync-dev (>= 1.6-~~) <!nocheck>,
libssl-dev <!nocheck>,
uuid-dev <!nocheck>
Maintainer: Proxmox Support Team <support@proxmox.com>
Standards-Version: 4.6.1
Vcs-Git: git://git.proxmox.com/git/proxmox.git
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
X-Cargo-Crate: proxmox-async
Rules-Requires-Root: no
Package: librust-proxmox-async-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-anyhow-1+default-dev,
librust-futures-0.3+default-dev,
librust-lazy-static-1+default-dev (>= 1.4-~~),
librust-pin-utils-0.1+default-dev,
librust-proxmox-io-1+default-dev,
librust-proxmox-io-1+tokio-dev,
librust-proxmox-lang-1+default-dev (>= 1.1-~~),
librust-tokio-1+default-dev (>= 1.6-~~),
librust-tokio-1+net-dev (>= 1.6-~~),
librust-tokio-1+rt-dev (>= 1.6-~~),
librust-tokio-1+rt-multi-thread-dev (>= 1.6-~~),
librust-tokio-1+sync-dev (>= 1.6-~~),
libssl-dev,
uuid-dev
Provides:
librust-proxmox-async+default-dev (= ${binary:Version}),
librust-proxmox-async-0-dev (= ${binary:Version}),
librust-proxmox-async-0+default-dev (= ${binary:Version}),
librust-proxmox-async-0.4-dev (= ${binary:Version}),
librust-proxmox-async-0.4+default-dev (= ${binary:Version}),
librust-proxmox-async-0.4.1-dev (= ${binary:Version}),
librust-proxmox-async-0.4.1+default-dev (= ${binary:Version})
Description: Proxmox async/tokio helpers - Rust source code
This package contains the source for the Rust proxmox-async crate, packaged by
debcargo for use with cargo and dh-cargo.

View File

@ -1,18 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Files:
*
Copyright: 2019 - 2023 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/>.

View File

@ -1,10 +0,0 @@
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"
[packages.lib]
depends = [ "libssl-dev", "uuid-dev" ]

View File

@ -1,14 +0,0 @@
//! Async wrappers for blocking I/O (adding `block_in_place` around
//! channels/readers)
mod std_channel_stream;
pub use std_channel_stream::StdChannelStream;
mod tokio_writer_adapter;
pub use tokio_writer_adapter::TokioWriterAdapter;
mod wrapped_reader_stream;
pub use wrapped_reader_stream::WrappedReaderStream;
mod sender_writer;
pub use sender_writer::SenderWriter;

View File

@ -1,47 +0,0 @@
use std::io;
use anyhow::Error;
use tokio::sync::mpsc::Sender;
/// Wrapper struct around [`tokio::sync::mpsc::Sender`] for `Result<Vec<u8>, Error>` that implements [`std::io::Write`]
pub struct SenderWriter {
sender: Sender<Result<Vec<u8>, Error>>,
}
impl SenderWriter {
pub fn from_sender(sender: tokio::sync::mpsc::Sender<Result<Vec<u8>, Error>>) -> Self {
Self { sender }
}
fn write_impl(&mut self, buf: &[u8]) -> io::Result<usize> {
if let Err(err) = self.sender.blocking_send(Ok(buf.to_vec())) {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
format!("could not send: {}", err),
));
}
Ok(buf.len())
}
fn flush_impl(&mut self) -> io::Result<()> {
Ok(())
}
}
impl io::Write for SenderWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.write_impl(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.flush_impl()
}
}
impl Drop for SenderWriter {
fn drop(&mut self) {
// ignore errors
let _ = self.flush_impl();
}
}

View File

@ -1,21 +0,0 @@
use std::pin::Pin;
use std::sync::mpsc::Receiver;
use std::task::{Context, Poll};
use futures::stream::Stream;
use crate::runtime::block_in_place;
/// Wrapper struct to convert a sync channel [Receiver] into a [Stream]
pub struct StdChannelStream<T>(pub Receiver<T>);
impl<T> Stream for StdChannelStream<T> {
type Item = T;
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Option<Self::Item>> {
match block_in_place(|| self.0.recv()) {
Ok(data) => Poll::Ready(Some(data)),
Err(_) => Poll::Ready(None), // channel closed
}
}
}

View File

@ -1,26 +0,0 @@
use std::io::Write;
use tokio::task::block_in_place;
/// Wrapper around [Write] which adds [block_in_place]
///
/// wraps each write with a [block_in_place] so that
/// any (blocking) writer can be safely used in async context in a
/// tokio runtime
pub struct TokioWriterAdapter<W: Write>(W);
impl<W: Write> TokioWriterAdapter<W> {
pub fn new(writer: W) -> Self {
Self(writer)
}
}
impl<W: Write> Write for TokioWriterAdapter<W> {
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
block_in_place(|| self.0.write(buf))
}
fn flush(&mut self) -> Result<(), std::io::Error> {
block_in_place(|| self.0.flush())
}
}

View File

@ -1,82 +0,0 @@
use std::io::{self, Read};
use std::pin::Pin;
use std::task::{Context, Poll};
use futures::stream::Stream;
use proxmox_io::vec;
use crate::runtime::block_in_place;
/// Wrapper struct to convert sync [Read] into a [Stream]
pub struct WrappedReaderStream<R: Read + Unpin> {
reader: R,
buffer: Vec<u8>,
}
impl<R: Read + Unpin> WrappedReaderStream<R> {
pub fn new(reader: R) -> Self {
Self {
reader,
buffer: vec::undefined(64 * 1024),
}
}
}
impl<R: Read + Unpin> Stream for WrappedReaderStream<R> {
type Item = Result<Vec<u8>, io::Error>;
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
match block_in_place(|| this.reader.read(&mut this.buffer)) {
Ok(n) => {
if n == 0 {
// EOF
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(this.buffer[..n].to_vec())))
}
}
Err(err) => Poll::Ready(Some(Err(err))),
}
}
}
#[cfg(test)]
mod test {
use std::io;
use anyhow::Error;
use futures::stream::TryStreamExt;
#[test]
fn test_wrapped_stream_reader() -> Result<(), Error> {
crate::runtime::main(async { run_wrapped_stream_reader_test().await })
}
struct DummyReader(usize);
impl io::Read for DummyReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0 += 1;
if self.0 >= 10 {
return Ok(0);
}
unsafe {
std::ptr::write_bytes(buf.as_mut_ptr(), 0, buf.len());
}
Ok(buf.len())
}
}
async fn run_wrapped_stream_reader_test() -> Result<(), Error> {
let mut reader = super::WrappedReaderStream::new(DummyReader(0));
while let Some(_data) = reader.try_next().await? {
// just waiting
}
Ok(())
}
}

View File

@ -1,188 +0,0 @@
//! Broadcast results to registered listeners
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use anyhow::{format_err, Error};
use futures::future::{FutureExt, TryFutureExt};
use tokio::sync::oneshot;
/// Broadcast results to registered listeners using asnyc oneshot channels
#[derive(Default)]
pub struct BroadcastData<T> {
result: Option<Result<T, String>>,
listeners: Vec<oneshot::Sender<Result<T, Error>>>,
}
impl<T: Clone> BroadcastData<T> {
pub fn new() -> Self {
Self {
result: None,
listeners: vec![],
}
}
pub fn notify_listeners(&mut self, result: Result<T, String>) {
self.result = Some(result.clone());
loop {
match self.listeners.pop() {
None => {
break;
}
Some(ch) => match &result {
Ok(result) => {
let _ = ch.send(Ok(result.clone()));
}
Err(err) => {
let _ = ch.send(Err(format_err!("{}", err)));
}
},
}
}
}
pub fn listen(&mut self) -> impl Future<Output = Result<T, Error>> {
use futures::future::{ok, Either};
match &self.result {
None => {}
Some(Ok(result)) => return Either::Left(ok(result.clone())),
Some(Err(err)) => return Either::Left(futures::future::err(format_err!("{}", err))),
}
let (tx, rx) = oneshot::channel::<Result<T, Error>>();
self.listeners.push(tx);
Either::Right(rx.map(|res| match res {
Ok(Ok(t)) => Ok(t),
Ok(Err(e)) => Err(e),
Err(e) => Err(Error::from(e)),
}))
}
}
type SourceFuture<T> = Pin<Box<dyn Future<Output = Result<T, Error>> + Send>>;
struct BroadCastFutureBinding<T> {
broadcast: BroadcastData<T>,
future: Option<SourceFuture<T>>,
}
/// Broadcast future results to registered listeners
pub struct BroadcastFuture<T> {
inner: Arc<Mutex<BroadCastFutureBinding<T>>>,
}
impl<T: Clone + Send + 'static> BroadcastFuture<T> {
/// Create instance for specified source future.
///
/// The result of the future is sent to all registered listeners.
pub fn new(source: Box<dyn Future<Output = Result<T, Error>> + Send>) -> Self {
let inner = BroadCastFutureBinding {
broadcast: BroadcastData::new(),
future: Some(Pin::from(source)),
};
Self {
inner: Arc::new(Mutex::new(inner)),
}
}
/// Creates a new instance with a oneshot channel as trigger
pub fn new_oneshot() -> (Self, oneshot::Sender<Result<T, Error>>) {
let (tx, rx) = oneshot::channel::<Result<T, Error>>();
let rx = rx.map_err(Error::from).and_then(futures::future::ready);
(Self::new(Box::new(rx)), tx)
}
fn notify_listeners(inner: Arc<Mutex<BroadCastFutureBinding<T>>>, result: Result<T, String>) {
let mut data = inner.lock().unwrap();
data.broadcast.notify_listeners(result);
}
fn spawn(
inner: Arc<Mutex<BroadCastFutureBinding<T>>>,
) -> impl Future<Output = Result<T, Error>> {
let mut data = inner.lock().unwrap();
if let Some(source) = data.future.take() {
let inner1 = inner.clone();
let task = source.map(move |value| match value {
Ok(value) => Self::notify_listeners(inner1, Ok(value)),
Err(err) => Self::notify_listeners(inner1, Err(err.to_string())),
});
tokio::spawn(task);
}
data.broadcast.listen()
}
/// Register a listener
pub fn listen(&self) -> impl Future<Output = Result<T, Error>> {
let inner2 = self.inner.clone();
async move { Self::spawn(inner2).await }
}
}
#[test]
fn test_broadcast_future() {
use std::sync::atomic::{AtomicUsize, Ordering};
static CHECKSUM: AtomicUsize = AtomicUsize::new(0);
let (sender, trigger) = BroadcastFuture::new_oneshot();
let receiver1 = sender
.listen()
.map_ok(|res| {
CHECKSUM.fetch_add(res, Ordering::SeqCst);
})
.map_err(|err| {
panic!("got error {}", err);
})
.map(|_| ());
let receiver2 = sender
.listen()
.map_ok(|res| {
CHECKSUM.fetch_add(res * 2, Ordering::SeqCst);
})
.map_err(|err| {
panic!("got error {}", err);
})
.map(|_| ());
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async move {
let r1 = tokio::spawn(receiver1);
let r2 = tokio::spawn(receiver2);
trigger.send(Ok(1)).unwrap();
let _ = r1.await;
let _ = r2.await;
});
let result = CHECKSUM.load(Ordering::SeqCst);
assert_eq!(result, 3);
// the result stays available until the BroadcastFuture is dropped
rt.block_on(
sender
.listen()
.map_ok(|res| {
CHECKSUM.fetch_add(res * 4, Ordering::SeqCst);
})
.map_err(|err| {
panic!("got error {}", err);
})
.map(|_| ()),
);
let result = CHECKSUM.load(Ordering::SeqCst);
assert_eq!(result, 7);
}

View File

@ -1,108 +0,0 @@
//! Wrappers between async readers and streams.
use std::future::Future;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use anyhow::{Error, Result};
use futures::future::FutureExt;
use futures::ready;
use tokio::io::AsyncWrite;
use tokio::sync::mpsc::Sender;
use proxmox_io::ByteBuffer;
use proxmox_lang::{error::io_err_other, io_format_err};
/// Wrapper around tokio::sync::mpsc::Sender, which implements Write
pub struct AsyncChannelWriter {
sender: Option<Sender<Result<Vec<u8>, Error>>>,
buf: ByteBuffer,
state: WriterState,
}
type SendResult = io::Result<Sender<Result<Vec<u8>>>>;
enum WriterState {
Ready,
Sending(Pin<Box<dyn Future<Output = SendResult> + Send + 'static>>),
}
impl AsyncChannelWriter {
pub fn new(sender: Sender<Result<Vec<u8>, Error>>, buf_size: usize) -> Self {
Self {
sender: Some(sender),
buf: ByteBuffer::with_capacity(buf_size),
state: WriterState::Ready,
}
}
fn poll_write_impl(
&mut self,
cx: &mut Context,
buf: &[u8],
flush: bool,
) -> Poll<io::Result<usize>> {
loop {
match &mut self.state {
WriterState::Ready => {
if flush {
if self.buf.is_empty() {
return Poll::Ready(Ok(0));
}
} else {
let free_size = self.buf.free_size();
if free_size > buf.len() || self.buf.is_empty() {
let count = free_size.min(buf.len());
self.buf.get_free_mut_slice()[..count].copy_from_slice(&buf[..count]);
self.buf.add_size(count);
return Poll::Ready(Ok(count));
}
}
let sender = match self.sender.take() {
Some(sender) => sender,
None => return Poll::Ready(Err(io_err_other("no sender"))),
};
let data = self.buf.remove_data(self.buf.len()).to_vec();
let future = async move {
sender
.send(Ok(data))
.await
.map(move |_| sender)
.map_err(|err| io_format_err!("could not send: {}", err))
};
self.state = WriterState::Sending(future.boxed());
}
WriterState::Sending(ref mut future) => match ready!(future.as_mut().poll(cx)) {
Ok(sender) => {
self.sender = Some(sender);
self.state = WriterState::Ready;
}
Err(err) => return Poll::Ready(Err(err)),
},
}
}
}
}
impl AsyncWrite for AsyncChannelWriter {
fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll<io::Result<usize>> {
let this = self.get_mut();
this.poll_write_impl(cx, buf, false)
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
let this = self.get_mut();
match ready!(this.poll_write_impl(cx, &[], true)) {
Ok(_) => Poll::Ready(Ok(())),
Err(err) => Poll::Ready(Err(err)),
}
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
self.poll_flush(cx)
}
}

View File

@ -1,4 +0,0 @@
//! Helper which implements AsyncRead/AsyncWrite
mod async_channel_writer;
pub use async_channel_writer::AsyncChannelWriter;

View File

@ -1,6 +0,0 @@
pub mod blocking;
pub mod broadcast_future;
pub mod io;
pub mod net;
pub mod runtime;
pub mod stream;

View File

@ -1 +0,0 @@
pub mod udp;

View File

@ -1,36 +0,0 @@
use std::io;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
use tokio::net::{ToSocketAddrs, UdpSocket};
/// Helper to connect to UDP addresses without having to manually bind to the correct ip address
pub async fn connect<A: ToSocketAddrs>(addr: A) -> io::Result<UdpSocket> {
let mut last_err = None;
for address in tokio::net::lookup_host(&addr).await? {
let bind_address = match address {
SocketAddr::V4(_) => SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0),
SocketAddr::V6(_) => SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 0),
};
let socket = match UdpSocket::bind(bind_address).await {
Ok(sock) => sock,
Err(err) => {
last_err = Some(err);
continue;
}
};
match socket.connect(address).await {
Ok(()) => return Ok(socket),
Err(err) => {
last_err = Some(err);
continue;
}
}
}
Err(last_err.unwrap_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"could not resolve to any addresses",
)
}))
}

View File

@ -1,182 +0,0 @@
//! Helpers for quirks of the current tokio runtime.
use std::cell::RefCell;
use std::future::Future;
use std::sync::{Arc, Mutex, Weak};
use std::task::{Context, Poll, Waker};
use std::thread::{self, Thread};
use lazy_static::lazy_static;
use pin_utils::pin_mut;
use tokio::runtime::{self, Runtime};
thread_local! {
static BLOCKING: RefCell<bool> = RefCell::new(false);
}
fn is_in_tokio() -> bool {
tokio::runtime::Handle::try_current().is_ok()
}
fn is_blocking() -> bool {
BLOCKING.with(|v| *v.borrow())
}
struct BlockingGuard(bool);
impl BlockingGuard {
fn set() -> Self {
Self(BLOCKING.with(|v| {
let old = *v.borrow();
*v.borrow_mut() = true;
old
}))
}
}
impl Drop for BlockingGuard {
fn drop(&mut self) {
BLOCKING.with(|v| {
*v.borrow_mut() = self.0;
});
}
}
lazy_static! {
// avoid openssl bug: https://github.com/openssl/openssl/issues/6214
// by dropping the runtime as early as possible
static ref RUNTIME: Mutex<Weak<Runtime>> = Mutex::new(Weak::new());
}
#[link(name = "crypto")]
extern "C" {
fn OPENSSL_thread_stop();
}
/// Get or create the current main tokio runtime.
///
/// This makes sure that tokio's worker threads are marked for us so that we know whether we
/// can/need to use `block_in_place` in our `block_on` helper.
pub fn get_runtime_with_builder<F: Fn() -> runtime::Builder>(get_builder: F) -> Arc<Runtime> {
let mut guard = RUNTIME.lock().unwrap();
if let Some(rt) = guard.upgrade() {
return rt;
}
let mut builder = get_builder();
builder.on_thread_stop(|| {
// avoid openssl bug: https://github.com/openssl/openssl/issues/6214
// call OPENSSL_thread_stop to avoid race with openssl cleanup handlers
unsafe {
OPENSSL_thread_stop();
}
});
let runtime = builder.build().expect("failed to spawn tokio runtime");
let rt = Arc::new(runtime);
*guard = Arc::downgrade(&rt);
rt
}
/// Get or create the current main tokio runtime.
///
/// This calls get_runtime_with_builder() using the tokio default threaded scheduler
pub fn get_runtime() -> Arc<Runtime> {
get_runtime_with_builder(|| {
let mut builder = runtime::Builder::new_multi_thread();
builder.enable_all();
builder
})
}
/// Block on a synchronous piece of code.
pub fn block_in_place<R>(fut: impl FnOnce() -> R) -> R {
// don't double-exit the context (tokio doesn't like that)
// also, if we're not actually in a tokio-worker we must not use block_in_place() either
if is_blocking() || !is_in_tokio() {
fut()
} else {
// we are in an actual tokio worker thread, block it:
tokio::task::block_in_place(move || {
let _guard = BlockingGuard::set();
fut()
})
}
}
/// Block on a future in this thread.
pub fn block_on<F: Future>(fut: F) -> F::Output {
// don't double-exit the context (tokio doesn't like that)
if is_blocking() {
block_on_local_future(fut)
} else if is_in_tokio() {
// inside a tokio worker we need to tell tokio that we're about to really block:
tokio::task::block_in_place(move || {
let _guard = BlockingGuard::set();
block_on_local_future(fut)
})
} else {
// not a worker thread, not associated with a runtime, make sure we have a runtime (spawn
// it on demand if necessary), then enter it
let _guard = BlockingGuard::set();
let _enter_guard = get_runtime().enter();
get_runtime().block_on(fut)
}
}
/*
fn block_on_impl<F>(mut fut: F) -> F::Output
where
F: Future + Send,
F::Output: Send + 'static,
{
let (tx, rx) = tokio::sync::oneshot::channel();
let fut_ptr = &mut fut as *mut F as usize; // hack to not require F to be 'static
tokio::spawn(async move {
let fut: F = unsafe { std::ptr::read(fut_ptr as *mut F) };
tx
.send(fut.await)
.map_err(drop)
.expect("failed to send block_on result to channel")
});
futures::executor::block_on(async move {
rx.await.expect("failed to receive block_on result from channel")
})
std::mem::forget(fut);
}
*/
/// This used to be our tokio main entry point. Now this just calls out to `block_on` for
/// compatibility, which will perform all the necessary tasks on-demand anyway.
pub fn main<F: Future>(fut: F) -> F::Output {
block_on(fut)
}
struct ThreadWaker(Thread);
impl std::task::Wake for ThreadWaker {
fn wake(self: Arc<Self>) {
self.0.unpark();
}
fn wake_by_ref(self: &Arc<Self>) {
self.0.unpark();
}
}
fn block_on_local_future<F: Future>(fut: F) -> F::Output {
pin_mut!(fut);
let waker = Waker::from(Arc::new(ThreadWaker(thread::current())));
let mut context = Context::from_waker(&waker);
loop {
match fut.as_mut().poll(&mut context) {
Poll::Ready(out) => return out,
Poll::Pending => thread::park(),
}
}
}

Some files were not shown because too many files have changed in this diff Show More