* [pdm-devel] [PATCH datacenter-manager 1/3] pdm-api-types: views: preparations for future glob/regex support
2025-11-17 14:11 [pdm-devel] [PATCH datacenter-manager 0/3] views: preparations for regex/glob, include-all param Lukas Wagner
@ 2025-11-17 14:11 ` Lukas Wagner
2025-11-17 14:11 ` [pdm-devel] [PATCH datacenter-manager 2/3] views: add 'include-all' param; change semantics when there are no includes Lukas Wagner
2025-11-17 14:11 ` [pdm-devel] [PATCH datacenter-manager 3/3] views: tests: use full section-config format for test cases Lukas Wagner
2 siblings, 0 replies; 4+ messages in thread
From: Lukas Wagner @ 2025-11-17 14:11 UTC (permalink / raw)
To: pdm-devel
Change the config format slightly in a way similar to how the
'match-field' statements work for notification matchers.
The new format is, in pseudo-regex:
(include|exclude) (exact:)?(resource-pool|...|tag)=.*
The 'exact:' part is optional. Later we can add "regex" or "glob" types
to add support for globbing or regex without any changes to the config
format.
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
lib/pdm-api-types/src/views.rs | 185 +++++++++++++++++++++++----------
server/src/views/mod.rs | 24 +++--
server/src/views/tests.rs | 110 ++++++++++++--------
3 files changed, 215 insertions(+), 104 deletions(-)
diff --git a/lib/pdm-api-types/src/views.rs b/lib/pdm-api-types/src/views.rs
index ef39cc62..950a3a04 100644
--- a/lib/pdm-api-types/src/views.rs
+++ b/lib/pdm-api-types/src/views.rs
@@ -1,6 +1,6 @@
-use std::{fmt::Display, str::FromStr, sync::OnceLock};
+use std::{fmt::Debug, fmt::Display, str::FromStr, sync::OnceLock};
-use anyhow::bail;
+use anyhow::{bail, Error};
use const_format::concatcp;
use serde::{Deserialize, Serialize};
@@ -23,11 +23,11 @@ const_regex! {
pub const FILTER_RULE_SCHEMA: Schema = StringSchema::new("Filter rule for resources.")
.format(&ApiStringFormat::VerifyFn(verify_filter_rule))
.type_text(
- "resource-type:<storage|qemu|lxc|sdn-zone|datastore|node>\
- |resource-pool:<pool-name>\
- |tag:<tag-name>\
- |remote:<remote-name>\
- |resource-id:<resource-id>",
+ "[exact:]resource-type=<storage|qemu|lxc|sdn-zone|datastore|node>\
+ |[exact:]resource-pool=<pool-name>\
+ |[exact:]tag=<tag-name>\
+ |[exact:]remote=<remote-name>\
+ |[exact:]resource=id:<resource-id>",
)
.schema();
@@ -102,65 +102,112 @@ impl ApiSectionDataEntry for ViewConfigEntry {
}
}
+#[derive(Clone, Debug, PartialEq)]
+/// Matcher for string-based values.
+pub enum StringMatcher {
+ Exact(String),
+}
+
+impl StringMatcher {
+ /// Check if a given string matches.
+ pub fn matches(&self, value: &str) -> bool {
+ match self {
+ StringMatcher::Exact(matched_value) => value == matched_value,
+ }
+ }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+/// Matcher for enum-based values.
+pub struct EnumMatcher<T: PartialEq + Clone + Debug>(pub T);
+
+impl<T: PartialEq + Debug + Clone> EnumMatcher<T> {
+ /// Check if a given value matches.
+ pub fn matches(&self, value: &T) -> bool {
+ self.0 == *value
+ }
+}
+
#[derive(Clone, Debug, PartialEq)]
/// Filter rule for includes/excludes.
pub enum FilterRule {
/// Match a resource type.
- ResourceType(ResourceType),
+ ResourceType(EnumMatcher<ResourceType>),
/// Match a resource pools (for PVE guests).
- ResourcePool(String),
+ ResourcePool(StringMatcher),
/// Match a (global) resource ID, e.g. 'remote/<remote>/guest/<vmid>'.
- ResourceId(String),
+ ResourceId(StringMatcher),
/// Match a tag (for PVE guests).
- Tag(String),
+ Tag(StringMatcher),
/// Match a remote.
- Remote(String),
+ Remote(StringMatcher),
}
impl FromStr for FilterRule {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
- Ok(match s.split_once(':') {
- Some(("resource-type", value)) => FilterRule::ResourceType(value.parse()?),
- Some(("resource-pool", value)) => {
- if !PROXMOX_SAFE_ID_REGEX.is_match(value) {
- bail!("invalid resource-pool value: {value}");
- }
- FilterRule::ResourcePool(value.to_string())
- }
- Some(("resource-id", value)) => {
- if !GLOBAL_RESOURCE_ID_REGEX.is_match(value) {
- bail!("invalid resource-id value: {value}");
- }
-
- FilterRule::ResourceId(value.to_string())
- }
- Some(("tag", value)) => {
- if !PROXMOX_SAFE_ID_REGEX.is_match(value) {
- bail!("invalid tag value: {value}");
- }
- FilterRule::Tag(value.to_string())
- }
- Some(("remote", value)) => {
- let _ = REMOTE_ID_SCHEMA.parse_simple_value(value)?;
- FilterRule::Remote(value.to_string())
- }
- Some((ty, _)) => bail!("invalid type: {ty}"),
- None => bail!("invalid filter rule: {s}"),
- })
+ if let Some(s) = s.strip_prefix("exact:") {
+ parse_filter_rule(s)
+ } else {
+ parse_filter_rule(s)
+ }
}
}
+fn parse_filter_rule(s: &str) -> Result<FilterRule, Error> {
+ Ok(match s.split_once('=') {
+ Some(("resource-type", value)) => FilterRule::ResourceType(EnumMatcher(value.parse()?)),
+ Some(("resource-pool", value)) => {
+ if !PROXMOX_SAFE_ID_REGEX.is_match(value) {
+ bail!("invalid resource-pool value: {value}");
+ }
+
+ let val = StringMatcher::Exact(value.into());
+ FilterRule::ResourcePool(val)
+ }
+ Some(("resource-id", value)) => {
+ if !GLOBAL_RESOURCE_ID_REGEX.is_match(value) {
+ bail!("invalid resource-id value: {value}");
+ }
+
+ let val = StringMatcher::Exact(value.into());
+ FilterRule::ResourceId(val)
+ }
+ Some(("tag", value)) => {
+ if !PROXMOX_SAFE_ID_REGEX.is_match(value) {
+ bail!("invalid tag value: {value}");
+ }
+ let val = StringMatcher::Exact(value.into());
+ FilterRule::Tag(val)
+ }
+ Some(("remote", value)) => {
+ if !PROXMOX_SAFE_ID_REGEX.is_match(value) {
+ let _ = REMOTE_ID_SCHEMA.parse_simple_value(value)?;
+ }
+ let val = StringMatcher::Exact(value.into());
+ FilterRule::Remote(val)
+ }
+ Some((ty, _)) => bail!("invalid type: {ty}"),
+ None => bail!("invalid filter rule: {s}"),
+ })
+}
+
// used for serializing below, caution!
impl Display for FilterRule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
- FilterRule::ResourceType(resource_type) => write!(f, "resource-type:{resource_type}"),
- FilterRule::ResourcePool(pool) => write!(f, "resource-pool:{pool}"),
- FilterRule::ResourceId(id) => write!(f, "resource-id:{id}"),
- FilterRule::Tag(tag) => write!(f, "tag:{tag}"),
- FilterRule::Remote(remote) => write!(f, "remote:{remote}"),
+ FilterRule::ResourceType(EnumMatcher(resource_type)) => {
+ write!(f, "exact:resource-type={resource_type}")
+ }
+ FilterRule::ResourceId(StringMatcher::Exact(value)) => {
+ write!(f, "exact:resource-id={value}")
+ }
+ FilterRule::Tag(StringMatcher::Exact(value)) => write!(f, "exact:tag={value}"),
+ FilterRule::Remote(StringMatcher::Exact(value)) => write!(f, "exact:remote={value}"),
+ FilterRule::ResourcePool(StringMatcher::Exact(value)) => {
+ write!(f, "exact:resource-pool={value}")
+ }
}
}
}
@@ -176,7 +223,9 @@ fn verify_filter_rule(input: &str) -> Result<(), anyhow::Error> {
mod test {
use anyhow::Error;
- use crate::views::FilterRule;
+ use proxmox_section_config::typed::ApiSectionDataEntry;
+
+ use super::*;
fn parse_and_check_display(input: &str) -> Result<bool, Error> {
let rule: FilterRule = input.parse()?;
@@ -190,21 +239,49 @@ mod test {
assert!(parse_and_check_display("abc:").is_err());
assert!(parse_and_check_display("resource-type:").is_err());
- assert!(parse_and_check_display("resource-type:lxc").unwrap());
- assert!(parse_and_check_display("resource-type:qemu").unwrap());
- assert!(parse_and_check_display("resource-type:abc").is_err());
+ assert!(parse_and_check_display("exact:resource-type=lxc").unwrap());
+ assert!(parse_and_check_display("exact:resource-type=qemu").unwrap());
+ assert!(parse_and_check_display("exact:resource-type=abc").is_err());
assert!(parse_and_check_display("resource-pool:").is_err());
- assert!(parse_and_check_display("resource-pool:somepool").unwrap());
+ assert!(parse_and_check_display("exact:resource-pool=somepool").unwrap());
assert!(parse_and_check_display("resource-id:").is_err());
- assert!(parse_and_check_display("resource-id:remote/someremote/guest/100").unwrap());
- assert!(parse_and_check_display("resource-id:remote").is_err());
+ assert!(parse_and_check_display("exact:resource-id=remote/someremote/guest/100").unwrap());
+ assert!(parse_and_check_display("exact:resource-id=remote").is_err());
assert!(parse_and_check_display("tag:").is_err());
- assert!(parse_and_check_display("tag:sometag").unwrap());
+ assert!(parse_and_check_display("exact:tag=sometag").unwrap());
- assert!(parse_and_check_display("remote:someremote").unwrap());
+ assert!(parse_and_check_display("exact:remote=someremote").unwrap());
assert!(parse_and_check_display("remote:a").is_err());
}
+
+ #[test]
+ fn config_smoke_test() {
+ let config = "
+view: some-view
+ include exact:remote=someremote
+ include remote=someremote
+ include resource-type=qemu
+ include exact:resource-type=qemu
+ include resource-id=remote/someremote/guest/100
+ include exact:resource-id=remote/someremote/guest/100
+ include tag=sometag
+ include exact:tag=sometag
+ include resource-pool=somepool
+ include exact:resource-pool=somepool
+ exclude remote=someremote
+ exclude exact:remote=someremote
+ exclude resource-type=qemu
+ exclude exact:resource-type=qemu
+ exclude resource-id=remote/someremote/guest/100
+ exclude exact:resource-id=remote/someremote/guest/100
+ exclude tag=sometag
+ exclude exact:tag=sometag
+ exclude resource-pool=somepool
+ exclude exact:resource-pool=somepool
+";
+ ViewConfigEntry::parse_section_config("views.cfg", config).unwrap();
+ }
}
diff --git a/server/src/views/mod.rs b/server/src/views/mod.rs
index 80f8425c..8b4c70a4 100644
--- a/server/src/views/mod.rs
+++ b/server/src/views/mod.rs
@@ -72,7 +72,7 @@ impl View {
for include in &self.config.include {
if let FilterRule::Remote(r) = include {
has_any_include_remote = true;
- if r == remote {
+ if r.matches(remote) {
matches_any_include_remote = true;
break;
}
@@ -145,7 +145,7 @@ impl View {
fn matches_remote_rule(remote: &str, rule: &FilterRule) -> bool {
if let FilterRule::Remote(r) = rule {
- r == remote
+ r.matches(remote)
} else {
false
}
@@ -154,17 +154,23 @@ impl View {
fn check_rules(rules: &[FilterRule], remote: &str, resource: &ResourceData) -> bool {
rules.iter().any(|rule| match rule {
- FilterRule::ResourceType(resource_type) => resource.resource_type == *resource_type,
- FilterRule::ResourcePool(pool) => resource.resource_pool == Some(pool),
- FilterRule::ResourceId(resource_id) => resource.resource_id == resource_id,
- FilterRule::Tag(tag) => {
- if let Some(resource_tags) = resource.tags {
- resource_tags.contains(tag)
+ FilterRule::ResourceType(resource_type) => resource_type.matches(&resource.resource_type),
+ FilterRule::ResourcePool(pool) => {
+ if let Some(resource_pool) = resource.resource_pool {
+ pool.matches(resource_pool)
} else {
false
}
}
- FilterRule::Remote(included_remote) => included_remote == remote,
+ FilterRule::ResourceId(resource_id) => resource_id.matches(resource.resource_id),
+ FilterRule::Tag(tag) => {
+ if let Some(resource_tags) = resource.tags {
+ resource_tags.iter().any(|t| tag.matches(t))
+ } else {
+ false
+ }
+ }
+ FilterRule::Remote(included_remote) => included_remote.matches(remote),
})
}
diff --git a/server/src/views/tests.rs b/server/src/views/tests.rs
index 030b7994..14d94ac9 100644
--- a/server/src/views/tests.rs
+++ b/server/src/views/tests.rs
@@ -1,6 +1,6 @@
use pdm_api_types::{
resource::{PveLxcResource, PveQemuResource, PveStorageResource, Resource, ResourceType},
- views::{FilterRule, ViewConfig},
+ views::{EnumMatcher, FilterRule, StringMatcher, ViewConfig},
};
use super::View;
@@ -88,8 +88,8 @@ fn include_remotes() {
let config = ViewConfig {
id: "only-includes".into(),
include: vec![
- FilterRule::Remote("remote-a".into()),
- FilterRule::Remote("remote-b".into()),
+ FilterRule::Remote(StringMatcher::Exact("remote-a".into())),
+ FilterRule::Remote(StringMatcher::Exact("remote-b".into())),
],
..Default::default()
};
@@ -132,8 +132,8 @@ fn exclude_remotes() {
let config = ViewConfig {
id: "only-excludes".into(),
exclude: vec![
- FilterRule::Remote("remote-a".into()),
- FilterRule::Remote("remote-b".into()),
+ FilterRule::Remote(StringMatcher::Exact("remote-a".into())),
+ FilterRule::Remote(StringMatcher::Exact("remote-b".into())),
],
..Default::default()
};
@@ -177,12 +177,12 @@ fn include_exclude_remotes() {
let config = ViewConfig {
id: "both".into(),
include: vec![
- FilterRule::Remote("remote-a".into()),
- FilterRule::Remote("remote-b".into()),
+ FilterRule::Remote(StringMatcher::Exact("remote-a".into())),
+ FilterRule::Remote(StringMatcher::Exact("remote-b".into())),
],
exclude: vec![
- FilterRule::Remote("remote-b".into()),
- FilterRule::Remote("remote-c".into()),
+ FilterRule::Remote(StringMatcher::Exact("remote-b".into())),
+ FilterRule::Remote(StringMatcher::Exact("remote-c".into())),
],
};
run_test(
@@ -270,8 +270,8 @@ fn include_type() {
ViewConfig {
id: "include-resource-type".into(),
include: vec![
- FilterRule::ResourceType(ResourceType::PveStorage),
- FilterRule::ResourceType(ResourceType::PveQemu),
+ FilterRule::ResourceType(EnumMatcher(ResourceType::PveStorage)),
+ FilterRule::ResourceType(EnumMatcher(ResourceType::PveQemu)),
],
..Default::default()
},
@@ -298,8 +298,8 @@ fn exclude_type() {
ViewConfig {
id: "exclude-resource-type".into(),
exclude: vec![
- FilterRule::ResourceType(ResourceType::PveStorage),
- FilterRule::ResourceType(ResourceType::PveQemu),
+ FilterRule::ResourceType(EnumMatcher(ResourceType::PveStorage)),
+ FilterRule::ResourceType(EnumMatcher(ResourceType::PveQemu)),
],
..Default::default()
},
@@ -325,8 +325,10 @@ fn include_exclude_type() {
run_test(
ViewConfig {
id: "exclude-resource-type".into(),
- include: vec![FilterRule::ResourceType(ResourceType::PveQemu)],
- exclude: vec![FilterRule::ResourceType(ResourceType::PveStorage)],
+ include: vec![FilterRule::ResourceType(EnumMatcher(ResourceType::PveQemu))],
+ exclude: vec![FilterRule::ResourceType(EnumMatcher(
+ ResourceType::PveStorage,
+ ))],
},
&[
(
@@ -351,10 +353,10 @@ fn include_exclude_tags() {
ViewConfig {
id: "include-tags".into(),
include: vec![
- FilterRule::Tag("tag1".to_string()),
- FilterRule::Tag("tag2".to_string()),
+ FilterRule::Tag(StringMatcher::Exact("tag1".to_string())),
+ FilterRule::Tag(StringMatcher::Exact("tag2".to_string())),
],
- exclude: vec![FilterRule::Tag("tag3".to_string())],
+ exclude: vec![FilterRule::Tag(StringMatcher::Exact("tag3".to_string()))],
},
&[
(
@@ -396,10 +398,12 @@ fn include_exclude_resource_pool() {
ViewConfig {
id: "pools".into(),
include: vec![
- FilterRule::ResourcePool("pool1".to_string()),
- FilterRule::ResourcePool("pool2".to_string()),
+ FilterRule::ResourcePool(StringMatcher::Exact("pool1".to_string())),
+ FilterRule::ResourcePool(StringMatcher::Exact("pool2".to_string())),
],
- exclude: vec![FilterRule::ResourcePool("pool2".to_string())],
+ exclude: vec![FilterRule::ResourcePool(StringMatcher::Exact(
+ "pool2".to_string(),
+ ))],
},
&[
(
@@ -441,13 +445,19 @@ fn include_exclude_resource_id() {
ViewConfig {
id: "resource-id".into(),
include: vec![
- FilterRule::ResourceId(format!("remote/{REMOTE}/guest/100")),
- FilterRule::ResourceId(format!("remote/{REMOTE}/storage/{NODE}/{STORAGE}")),
+ FilterRule::ResourceId(StringMatcher::Exact(format!("remote/{REMOTE}/guest/100"))),
+ FilterRule::ResourceId(StringMatcher::Exact(format!(
+ "remote/{REMOTE}/storage/{NODE}/{STORAGE}"
+ ))),
],
exclude: vec![
- FilterRule::ResourceId(format!("remote/{REMOTE}/guest/101")),
- FilterRule::ResourceId("remote/otherremote/guest/101".to_string()),
- FilterRule::ResourceId(format!("remote/{REMOTE}/storage/{NODE}/otherstorage")),
+ FilterRule::ResourceId(StringMatcher::Exact(format!("remote/{REMOTE}/guest/101"))),
+ FilterRule::ResourceId(StringMatcher::Exact(
+ "remote/otherremote/guest/101".to_string(),
+ )),
+ FilterRule::ResourceId(StringMatcher::Exact(format!(
+ "remote/{REMOTE}/storage/{NODE}/otherstorage"
+ ))),
],
},
&[
@@ -491,10 +501,14 @@ fn node_included() {
id: "both".into(),
include: vec![
- FilterRule::Remote("remote-a".to_string()),
- FilterRule::ResourceId("remote/someremote/node/test".to_string()),
+ FilterRule::Remote(StringMatcher::Exact("remote-a".to_string())),
+ FilterRule::ResourceId(StringMatcher::Exact(
+ "remote/someremote/node/test".to_string(),
+ )),
],
- exclude: vec![FilterRule::Remote("remote-b".to_string())],
+ exclude: vec![FilterRule::Remote(StringMatcher::Exact(
+ "remote-b".to_string(),
+ ))],
});
assert!(view.is_node_included("remote-a", "somenode"));
@@ -511,7 +525,9 @@ fn can_skip_remote_if_excluded() {
let view = View::new(ViewConfig {
id: "abc".into(),
include: vec![],
- exclude: vec![FilterRule::Remote("remote-b".to_string())],
+ exclude: vec![FilterRule::Remote(StringMatcher::Exact(
+ "remote-b".to_string(),
+ ))],
});
assert!(!view.can_skip_remote("remote-a"));
@@ -522,7 +538,9 @@ fn can_skip_remote_if_excluded() {
fn can_skip_remote_if_included() {
let view = View::new(ViewConfig {
id: "abc".into(),
- include: vec![FilterRule::Remote("remote-b".to_string())],
+ include: vec![FilterRule::Remote(StringMatcher::Exact(
+ "remote-b".to_string(),
+ ))],
exclude: vec![],
});
@@ -535,8 +553,10 @@ fn can_skip_remote_cannot_skip_if_any_other_include() {
let view = View::new(ViewConfig {
id: "abc".into(),
include: vec![
- FilterRule::Remote("remote-b".to_string()),
- FilterRule::ResourceId("resource/remote-a/guest/100".to_string()),
+ FilterRule::Remote(StringMatcher::Exact("remote-b".to_string())),
+ FilterRule::ResourceId(StringMatcher::Exact(
+ "resource/remote-a/guest/100".to_string(),
+ )),
],
exclude: vec![],
});
@@ -549,10 +569,12 @@ fn can_skip_remote_cannot_skip_if_any_other_include() {
fn can_skip_remote_explicit_remote_exclude() {
let view = View::new(ViewConfig {
id: "abc".into(),
- include: vec![FilterRule::ResourceId(
+ include: vec![FilterRule::ResourceId(StringMatcher::Exact(
"resource/remote-a/guest/100".to_string(),
- )],
- exclude: vec![FilterRule::Remote("remote-a".to_string())],
+ ))],
+ exclude: vec![FilterRule::Remote(StringMatcher::Exact(
+ "remote-a".to_string(),
+ ))],
});
assert!(view.can_skip_remote("remote-a"));
@@ -574,9 +596,9 @@ fn can_skip_remote_with_empty_config() {
fn can_skip_remote_with_no_remote_includes() {
let view = View::new(ViewConfig {
id: "abc".into(),
- include: vec![FilterRule::ResourceId(
+ include: vec![FilterRule::ResourceId(StringMatcher::Exact(
"resource/remote-a/guest/100".to_string(),
- )],
+ ))],
exclude: vec![],
});
@@ -588,7 +610,9 @@ fn can_skip_remote_with_no_remote_includes() {
fn explicitly_included_remote() {
let view = View::new(ViewConfig {
id: "abc".into(),
- include: vec![FilterRule::Remote("remote-b".to_string())],
+ include: vec![FilterRule::Remote(StringMatcher::Exact(
+ "remote-b".to_string(),
+ ))],
exclude: vec![],
});
@@ -599,8 +623,12 @@ fn explicitly_included_remote() {
fn included_and_excluded_same_remote() {
let view = View::new(ViewConfig {
id: "abc".into(),
- include: vec![FilterRule::Remote("remote-b".to_string())],
- exclude: vec![FilterRule::Remote("remote-b".to_string())],
+ include: vec![FilterRule::Remote(StringMatcher::Exact(
+ "remote-b".to_string(),
+ ))],
+ exclude: vec![FilterRule::Remote(StringMatcher::Exact(
+ "remote-b".to_string(),
+ ))],
});
assert!(!view.is_remote_explicitly_included("remote-b"));
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 4+ messages in thread* [pdm-devel] [PATCH datacenter-manager 2/3] views: add 'include-all' param; change semantics when there are no includes
2025-11-17 14:11 [pdm-devel] [PATCH datacenter-manager 0/3] views: preparations for regex/glob, include-all param Lukas Wagner
2025-11-17 14:11 ` [pdm-devel] [PATCH datacenter-manager 1/3] pdm-api-types: views: preparations for future glob/regex support Lukas Wagner
@ 2025-11-17 14:11 ` Lukas Wagner
2025-11-17 14:11 ` [pdm-devel] [PATCH datacenter-manager 3/3] views: tests: use full section-config format for test cases Lukas Wagner
2 siblings, 0 replies; 4+ messages in thread
From: Lukas Wagner @ 2025-11-17 14:11 UTC (permalink / raw)
To: pdm-devel
Previously, having no 'include' rules would mean that *all* resources
are included. Due view permissions being transitively applied to all
included resources, this also has some security consequences.
For instance, if there is only a single include rule which is then
removed by accident, suddenly any AuthID having privileges on the view
ACL object would then be granted these privileges on *all* resources.
To somewhat mitigate this, the semantics are slightly changed. If there
are no include rules, then no resources will be contained in the view.
A new 'include-all' parameter is added, which aims to emphasize the
intent of *really* including everything.
Suggested-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
lib/pdm-api-types/src/views.rs | 4 +++
server/src/views/mod.rs | 60 +++++++++++++++++++---------------
server/src/views/tests.rs | 35 ++++++++++++++++++--
3 files changed, 69 insertions(+), 30 deletions(-)
diff --git a/lib/pdm-api-types/src/views.rs b/lib/pdm-api-types/src/views.rs
index 950a3a04..82ab8781 100644
--- a/lib/pdm-api-types/src/views.rs
+++ b/lib/pdm-api-types/src/views.rs
@@ -58,6 +58,9 @@ pub struct ViewConfig {
#[updater(skip)]
pub id: String,
+ /// Include all resources by default.
+ pub include_all: Option<bool>,
+
/// List of includes.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[updater(serde(skip_serializing_if = "Option::is_none"))]
@@ -261,6 +264,7 @@ mod test {
fn config_smoke_test() {
let config = "
view: some-view
+ include-all true
include exact:remote=someremote
include remote=someremote
include resource-type=qemu
diff --git a/server/src/views/mod.rs b/server/src/views/mod.rs
index 8b4c70a4..3669ace4 100644
--- a/server/src/views/mod.rs
+++ b/server/src/views/mod.rs
@@ -64,31 +64,34 @@ impl View {
/// When there are `include remote:<...>` or `exclude remote:<...>` rules, we can use these to
/// check if a remote needs to be considered at all.
pub fn can_skip_remote(&self, remote: &str) -> bool {
- let mut has_any_include_remote = false;
- let mut matches_any_include_remote = false;
-
- let mut any_other = false;
-
- for include in &self.config.include {
- if let FilterRule::Remote(r) = include {
- has_any_include_remote = true;
- if r.matches(remote) {
- matches_any_include_remote = true;
- break;
- }
- } else {
- any_other = true;
- }
- }
-
let matches_any_exclude_remote = self
.config
.exclude
.iter()
.any(|rule| Self::matches_remote_rule(remote, rule));
- (has_any_include_remote && !matches_any_include_remote && !any_other)
- || matches_any_exclude_remote
+ if matches_any_exclude_remote {
+ return true;
+ }
+
+ if self.config.include_all.unwrap_or_default() {
+ return false;
+ }
+
+ for include in &self.config.include {
+ if let FilterRule::Remote(r) = include {
+ if r.matches(remote) {
+ return false;
+ }
+ } else {
+ // If there is any other type of rule, we cannot safely infer whether we can skip
+ // the remote (e.g. for 'tag' matches, we have to check *all* remotes for resources
+ // with a given tag)
+ return false;
+ }
+ }
+
+ true
}
/// Check if a remote is *explicitly* included (and not excluded).
@@ -96,18 +99,22 @@ impl View {
/// A subset of the resources of a remote might still be pulled in by other rules,
/// but this function check if the remote as a whole is matched.
pub fn is_remote_explicitly_included(&self, remote: &str) -> bool {
- let matches_include_remote = self
- .config
- .include
- .iter()
- .any(|rule| Self::matches_remote_rule(remote, rule));
+ let included = if self.config.include_all.unwrap_or_default() {
+ true
+ } else {
+ self.config
+ .include
+ .iter()
+ .any(|rule| Self::matches_remote_rule(remote, rule))
+ };
+
let matches_exclude_remote = self
.config
.exclude
.iter()
.any(|rule| Self::matches_remote_rule(remote, rule));
- matches_include_remote && !matches_exclude_remote
+ included && !matches_exclude_remote
}
/// Check if a node is matched by the filter rules.
@@ -131,8 +138,7 @@ impl View {
}
fn check_if_included(&self, remote: &str, resource: &ResourceData) -> bool {
- if self.config.include.is_empty() {
- // If there are no include rules, any resource is included (unless excluded)
+ if self.config.include_all.unwrap_or_default() {
return true;
}
diff --git a/server/src/views/tests.rs b/server/src/views/tests.rs
index 14d94ac9..0d83ae70 100644
--- a/server/src/views/tests.rs
+++ b/server/src/views/tests.rs
@@ -135,6 +135,7 @@ fn exclude_remotes() {
FilterRule::Remote(StringMatcher::Exact("remote-a".into())),
FilterRule::Remote(StringMatcher::Exact("remote-b".into())),
],
+ include_all: Some(true),
..Default::default()
};
@@ -184,6 +185,8 @@ fn include_exclude_remotes() {
FilterRule::Remote(StringMatcher::Exact("remote-b".into())),
FilterRule::Remote(StringMatcher::Exact("remote-c".into())),
],
+
+ ..Default::default()
};
run_test(
config.clone(),
@@ -224,6 +227,7 @@ fn include_exclude_remotes() {
fn empty_config() {
let config = ViewConfig {
id: "empty".into(),
+ include_all: Some(true),
..Default::default()
};
run_test(
@@ -301,6 +305,7 @@ fn exclude_type() {
FilterRule::ResourceType(EnumMatcher(ResourceType::PveStorage)),
FilterRule::ResourceType(EnumMatcher(ResourceType::PveQemu)),
],
+ include_all: Some(true),
..Default::default()
},
&[
@@ -329,6 +334,7 @@ fn include_exclude_type() {
exclude: vec![FilterRule::ResourceType(EnumMatcher(
ResourceType::PveStorage,
))],
+ ..Default::default()
},
&[
(
@@ -357,6 +363,7 @@ fn include_exclude_tags() {
FilterRule::Tag(StringMatcher::Exact("tag2".to_string())),
],
exclude: vec![FilterRule::Tag(StringMatcher::Exact("tag3".to_string()))],
+ ..Default::default()
},
&[
(
@@ -404,6 +411,7 @@ fn include_exclude_resource_pool() {
exclude: vec![FilterRule::ResourcePool(StringMatcher::Exact(
"pool2".to_string(),
))],
+ ..Default::default()
},
&[
(
@@ -459,6 +467,7 @@ fn include_exclude_resource_id() {
"remote/{REMOTE}/storage/{NODE}/otherstorage"
))),
],
+ ..Default::default()
},
&[
(
@@ -509,6 +518,7 @@ fn node_included() {
exclude: vec![FilterRule::Remote(StringMatcher::Exact(
"remote-b".to_string(),
))],
+ ..Default::default()
});
assert!(view.is_node_included("remote-a", "somenode"));
@@ -528,6 +538,7 @@ fn can_skip_remote_if_excluded() {
exclude: vec![FilterRule::Remote(StringMatcher::Exact(
"remote-b".to_string(),
))],
+ include_all: Some(true),
});
assert!(!view.can_skip_remote("remote-a"));
@@ -542,6 +553,7 @@ fn can_skip_remote_if_included() {
"remote-b".to_string(),
))],
exclude: vec![],
+ ..Default::default()
});
assert!(!view.can_skip_remote("remote-b"));
@@ -559,6 +571,7 @@ fn can_skip_remote_cannot_skip_if_any_other_include() {
)),
],
exclude: vec![],
+ ..Default::default()
});
assert!(!view.can_skip_remote("remote-b"));
@@ -575,6 +588,7 @@ fn can_skip_remote_explicit_remote_exclude() {
exclude: vec![FilterRule::Remote(StringMatcher::Exact(
"remote-a".to_string(),
))],
+ ..Default::default()
});
assert!(view.can_skip_remote("remote-a"));
@@ -584,8 +598,19 @@ fn can_skip_remote_explicit_remote_exclude() {
fn can_skip_remote_with_empty_config() {
let view = View::new(ViewConfig {
id: "abc".into(),
- include: vec![],
- exclude: vec![],
+ ..Default::default()
+ });
+
+ assert!(view.can_skip_remote("remote-a"));
+ assert!(view.can_skip_remote("remote-b"));
+}
+
+#[test]
+fn can_skip_remote_cannot_skip_if_all_included() {
+ let view = View::new(ViewConfig {
+ id: "abc".into(),
+ include_all: Some(true),
+ ..Default::default()
});
assert!(!view.can_skip_remote("remote-a"));
@@ -600,6 +625,7 @@ fn can_skip_remote_with_no_remote_includes() {
"resource/remote-a/guest/100".to_string(),
))],
exclude: vec![],
+ ..Default::default()
});
assert!(!view.can_skip_remote("remote-a"));
@@ -614,6 +640,7 @@ fn explicitly_included_remote() {
"remote-b".to_string(),
))],
exclude: vec![],
+ ..Default::default()
});
assert!(view.is_remote_explicitly_included("remote-b"));
@@ -629,6 +656,7 @@ fn included_and_excluded_same_remote() {
exclude: vec![FilterRule::Remote(StringMatcher::Exact(
"remote-b".to_string(),
))],
+ ..Default::default()
});
assert!(!view.is_remote_explicitly_included("remote-b"));
@@ -640,8 +668,9 @@ fn not_explicitly_included_remote() {
id: "abc".into(),
include: vec![],
exclude: vec![],
+ include_all: Some(true),
});
// Assert that is not *explicitly* included
- assert!(!view.is_remote_explicitly_included("remote-b"));
+ assert!(view.is_remote_explicitly_included("remote-b"));
}
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 4+ messages in thread* [pdm-devel] [PATCH datacenter-manager 3/3] views: tests: use full section-config format for test cases
2025-11-17 14:11 [pdm-devel] [PATCH datacenter-manager 0/3] views: preparations for regex/glob, include-all param Lukas Wagner
2025-11-17 14:11 ` [pdm-devel] [PATCH datacenter-manager 1/3] pdm-api-types: views: preparations for future glob/regex support Lukas Wagner
2025-11-17 14:11 ` [pdm-devel] [PATCH datacenter-manager 2/3] views: add 'include-all' param; change semantics when there are no includes Lukas Wagner
@ 2025-11-17 14:11 ` Lukas Wagner
2 siblings, 0 replies; 4+ messages in thread
From: Lukas Wagner @ 2025-11-17 14:11 UTC (permalink / raw)
To: pdm-devel
The config structure should be more stable than the actual rust types,
so this should lead to less maintenance burden over time. Also, the
tests are a bit easier to read this way.
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
server/src/views/tests.rs | 378 +++++++++++++++++++-------------------
1 file changed, 186 insertions(+), 192 deletions(-)
diff --git a/server/src/views/tests.rs b/server/src/views/tests.rs
index 0d83ae70..9f496207 100644
--- a/server/src/views/tests.rs
+++ b/server/src/views/tests.rs
@@ -1,7 +1,8 @@
use pdm_api_types::{
- resource::{PveLxcResource, PveQemuResource, PveStorageResource, Resource, ResourceType},
- views::{EnumMatcher, FilterRule, StringMatcher, ViewConfig},
+ resource::{PveLxcResource, PveQemuResource, PveStorageResource, Resource},
+ views::{ViewConfig, ViewConfigEntry},
};
+use proxmox_section_config::typed::ApiSectionDataEntry;
use super::View;
@@ -79,20 +80,26 @@ fn run_test(config: ViewConfig, tests: &[((&str, &Resource), bool)]) {
}
}
+fn parse_config(config: &str) -> ViewConfig {
+ let config = ViewConfigEntry::parse_section_config("views.cfg", config).unwrap();
+ let ViewConfigEntry::View(config) = config.get("test").unwrap();
+ config.clone()
+}
+
const NODE: &str = "somenode";
const STORAGE: &str = "somestorage";
const REMOTE: &str = "someremote";
#[test]
fn include_remotes() {
- let config = ViewConfig {
- id: "only-includes".into(),
- include: vec![
- FilterRule::Remote(StringMatcher::Exact("remote-a".into())),
- FilterRule::Remote(StringMatcher::Exact("remote-b".into())),
- ],
- ..Default::default()
- };
+ let config = parse_config(
+ "
+view: test
+ include remote=remote-a
+ include remote=remote-b
+",
+ );
+
run_test(
config.clone(),
&[
@@ -129,15 +136,14 @@ fn include_remotes() {
#[test]
fn exclude_remotes() {
- let config = ViewConfig {
- id: "only-excludes".into(),
- exclude: vec![
- FilterRule::Remote(StringMatcher::Exact("remote-a".into())),
- FilterRule::Remote(StringMatcher::Exact("remote-b".into())),
- ],
- include_all: Some(true),
- ..Default::default()
- };
+ let config = parse_config(
+ "
+view: test
+ include-all true
+ exclude remote=remote-a
+ exclude remote=remote-b
+",
+ );
run_test(
config.clone(),
@@ -175,19 +181,16 @@ fn exclude_remotes() {
#[test]
fn include_exclude_remotes() {
- let config = ViewConfig {
- id: "both".into(),
- include: vec![
- FilterRule::Remote(StringMatcher::Exact("remote-a".into())),
- FilterRule::Remote(StringMatcher::Exact("remote-b".into())),
- ],
- exclude: vec![
- FilterRule::Remote(StringMatcher::Exact("remote-b".into())),
- FilterRule::Remote(StringMatcher::Exact("remote-c".into())),
- ],
+ let config = parse_config(
+ "
+view: test
+ include remote=remote-a
+ include remote=remote-b
+ exclude remote=remote-b
+ exclude remote=remote-c
+",
+ );
- ..Default::default()
- };
run_test(
config.clone(),
&[
@@ -225,11 +228,12 @@ fn include_exclude_remotes() {
#[test]
fn empty_config() {
- let config = ViewConfig {
- id: "empty".into(),
- include_all: Some(true),
- ..Default::default()
- };
+ let config = parse_config(
+ "
+view: test
+ include-all true
+",
+ );
run_test(
config.clone(),
&[
@@ -270,15 +274,15 @@ fn empty_config() {
#[test]
fn include_type() {
+ let config = parse_config(
+ "
+view: test
+ include resource-type=storage
+ include resource-type=qemu
+",
+ );
run_test(
- ViewConfig {
- id: "include-resource-type".into(),
- include: vec![
- FilterRule::ResourceType(EnumMatcher(ResourceType::PveStorage)),
- FilterRule::ResourceType(EnumMatcher(ResourceType::PveQemu)),
- ],
- ..Default::default()
- },
+ config,
&[
(
(REMOTE, &make_storage_resource(REMOTE, NODE, STORAGE)),
@@ -298,16 +302,16 @@ fn include_type() {
#[test]
fn exclude_type() {
+ let config = parse_config(
+ "
+view: test
+ include-all true
+ exclude resource-type=storage
+ exclude resource-type=qemu
+",
+ );
run_test(
- ViewConfig {
- id: "exclude-resource-type".into(),
- exclude: vec![
- FilterRule::ResourceType(EnumMatcher(ResourceType::PveStorage)),
- FilterRule::ResourceType(EnumMatcher(ResourceType::PveQemu)),
- ],
- include_all: Some(true),
- ..Default::default()
- },
+ config,
&[
(
(REMOTE, &make_storage_resource(REMOTE, NODE, STORAGE)),
@@ -327,15 +331,16 @@ fn exclude_type() {
#[test]
fn include_exclude_type() {
+ let config = parse_config(
+ "
+view: test
+ include resource-type=qemu
+ exclude resource-type=storage
+",
+ );
+
run_test(
- ViewConfig {
- id: "exclude-resource-type".into(),
- include: vec![FilterRule::ResourceType(EnumMatcher(ResourceType::PveQemu))],
- exclude: vec![FilterRule::ResourceType(EnumMatcher(
- ResourceType::PveStorage,
- ))],
- ..Default::default()
- },
+ config,
&[
(
(REMOTE, &make_storage_resource(REMOTE, NODE, STORAGE)),
@@ -355,16 +360,16 @@ fn include_exclude_type() {
#[test]
fn include_exclude_tags() {
+ let config = parse_config(
+ "
+view: test
+ include tag=tag1
+ include tag=tag2
+ exclude tag=tag3
+",
+ );
run_test(
- ViewConfig {
- id: "include-tags".into(),
- include: vec![
- FilterRule::Tag(StringMatcher::Exact("tag1".to_string())),
- FilterRule::Tag(StringMatcher::Exact("tag2".to_string())),
- ],
- exclude: vec![FilterRule::Tag(StringMatcher::Exact("tag3".to_string()))],
- ..Default::default()
- },
+ config,
&[
(
(REMOTE, &make_storage_resource(REMOTE, NODE, STORAGE)),
@@ -401,18 +406,16 @@ fn include_exclude_tags() {
#[test]
fn include_exclude_resource_pool() {
+ let config = parse_config(
+ "
+view: test
+ include resource-pool=pool1
+ include resource-pool=pool2
+ exclude resource-pool=pool2
+",
+ );
run_test(
- ViewConfig {
- id: "pools".into(),
- include: vec![
- FilterRule::ResourcePool(StringMatcher::Exact("pool1".to_string())),
- FilterRule::ResourcePool(StringMatcher::Exact("pool2".to_string())),
- ],
- exclude: vec![FilterRule::ResourcePool(StringMatcher::Exact(
- "pool2".to_string(),
- ))],
- ..Default::default()
- },
+ config,
&[
(
(REMOTE, &make_storage_resource(REMOTE, NODE, STORAGE)),
@@ -449,26 +452,18 @@ fn include_exclude_resource_pool() {
#[test]
fn include_exclude_resource_id() {
+ let config = parse_config(
+ "
+view: test
+ include resource-id=remote/someremote/guest/100
+ include resource-id=remote/someremote/storage/somenode/somestorage
+ exclude resource-id=remote/someremote/guest/101
+ exclude resource-id=remote/otherremote/guest/101
+ exclude resource-id=remote/someremote/storage/somenode/otherstorage
+",
+ );
run_test(
- ViewConfig {
- id: "resource-id".into(),
- include: vec![
- FilterRule::ResourceId(StringMatcher::Exact(format!("remote/{REMOTE}/guest/100"))),
- FilterRule::ResourceId(StringMatcher::Exact(format!(
- "remote/{REMOTE}/storage/{NODE}/{STORAGE}"
- ))),
- ],
- exclude: vec![
- FilterRule::ResourceId(StringMatcher::Exact(format!("remote/{REMOTE}/guest/101"))),
- FilterRule::ResourceId(StringMatcher::Exact(
- "remote/otherremote/guest/101".to_string(),
- )),
- FilterRule::ResourceId(StringMatcher::Exact(format!(
- "remote/{REMOTE}/storage/{NODE}/otherstorage"
- ))),
- ],
- ..Default::default()
- },
+ config,
&[
(
(REMOTE, &make_storage_resource(REMOTE, NODE, STORAGE)),
@@ -506,20 +501,16 @@ fn include_exclude_resource_id() {
#[test]
fn node_included() {
- let view = View::new(ViewConfig {
- id: "both".into(),
+ let config = parse_config(
+ "
+view: test
+ include remote=remote-a
+ include resource-id=remote/someremote/node/test
+ exclude remote=remote-b
+",
+ );
- include: vec![
- FilterRule::Remote(StringMatcher::Exact("remote-a".to_string())),
- FilterRule::ResourceId(StringMatcher::Exact(
- "remote/someremote/node/test".to_string(),
- )),
- ],
- exclude: vec![FilterRule::Remote(StringMatcher::Exact(
- "remote-b".to_string(),
- ))],
- ..Default::default()
- });
+ let view = View::new(config);
assert!(view.is_node_included("remote-a", "somenode"));
assert!(view.is_node_included("remote-a", "somenode2"));
@@ -527,19 +518,19 @@ fn node_included() {
assert!(!view.is_node_included("remote-b", "somenode2"));
assert!(view.is_node_included("someremote", "test"));
- assert_eq!(view.name(), "both");
+ assert_eq!(view.name(), "test");
}
#[test]
fn can_skip_remote_if_excluded() {
- let view = View::new(ViewConfig {
- id: "abc".into(),
- include: vec![],
- exclude: vec![FilterRule::Remote(StringMatcher::Exact(
- "remote-b".to_string(),
- ))],
- include_all: Some(true),
- });
+ let config = parse_config(
+ "
+view: test
+ include-all true
+ exclude remote=remote-b
+",
+ );
+ let view = View::new(config);
assert!(!view.can_skip_remote("remote-a"));
assert!(view.can_skip_remote("remote-b"));
@@ -547,14 +538,14 @@ fn can_skip_remote_if_excluded() {
#[test]
fn can_skip_remote_if_included() {
- let view = View::new(ViewConfig {
- id: "abc".into(),
- include: vec![FilterRule::Remote(StringMatcher::Exact(
- "remote-b".to_string(),
- ))],
- exclude: vec![],
- ..Default::default()
- });
+ let config = parse_config(
+ "
+view: test
+ include remote=remote-b
+",
+ );
+
+ let view = View::new(config);
assert!(!view.can_skip_remote("remote-b"));
assert!(view.can_skip_remote("remote-a"));
@@ -562,17 +553,15 @@ fn can_skip_remote_if_included() {
#[test]
fn can_skip_remote_cannot_skip_if_any_other_include() {
- let view = View::new(ViewConfig {
- id: "abc".into(),
- include: vec![
- FilterRule::Remote(StringMatcher::Exact("remote-b".to_string())),
- FilterRule::ResourceId(StringMatcher::Exact(
- "resource/remote-a/guest/100".to_string(),
- )),
- ],
- exclude: vec![],
- ..Default::default()
- });
+ let config = parse_config(
+ "
+view: test
+ include remote=remote-b
+ include resource-id=remote/remote-a/guest/100
+",
+ );
+
+ let view = View::new(config);
assert!(!view.can_skip_remote("remote-b"));
assert!(!view.can_skip_remote("remote-a"));
@@ -580,26 +569,28 @@ fn can_skip_remote_cannot_skip_if_any_other_include() {
#[test]
fn can_skip_remote_explicit_remote_exclude() {
- let view = View::new(ViewConfig {
- id: "abc".into(),
- include: vec![FilterRule::ResourceId(StringMatcher::Exact(
- "resource/remote-a/guest/100".to_string(),
- ))],
- exclude: vec![FilterRule::Remote(StringMatcher::Exact(
- "remote-a".to_string(),
- ))],
- ..Default::default()
- });
+ let config = parse_config(
+ "
+view: test
+ exclude remote=remote-a
+ include resource-id=remote/remote-a/guest/100
+",
+ );
+
+ let view = View::new(config);
assert!(view.can_skip_remote("remote-a"));
}
#[test]
fn can_skip_remote_with_empty_config() {
- let view = View::new(ViewConfig {
- id: "abc".into(),
- ..Default::default()
- });
+ let config = parse_config(
+ "
+view: test
+",
+ );
+
+ let view = View::new(config);
assert!(view.can_skip_remote("remote-a"));
assert!(view.can_skip_remote("remote-b"));
@@ -607,11 +598,14 @@ fn can_skip_remote_with_empty_config() {
#[test]
fn can_skip_remote_cannot_skip_if_all_included() {
- let view = View::new(ViewConfig {
- id: "abc".into(),
- include_all: Some(true),
- ..Default::default()
- });
+ let config = parse_config(
+ "
+view: test
+ include-all true
+",
+ );
+
+ let view = View::new(config);
assert!(!view.can_skip_remote("remote-a"));
assert!(!view.can_skip_remote("remote-b"));
@@ -619,14 +613,14 @@ fn can_skip_remote_cannot_skip_if_all_included() {
#[test]
fn can_skip_remote_with_no_remote_includes() {
- let view = View::new(ViewConfig {
- id: "abc".into(),
- include: vec![FilterRule::ResourceId(StringMatcher::Exact(
- "resource/remote-a/guest/100".to_string(),
- ))],
- exclude: vec![],
- ..Default::default()
- });
+ let config = parse_config(
+ "
+view: test
+ include resource-id=remote/remote-a/guest/100
+",
+ );
+
+ let view = View::new(config);
assert!(!view.can_skip_remote("remote-a"));
assert!(!view.can_skip_remote("remote-b"));
@@ -634,42 +628,42 @@ fn can_skip_remote_with_no_remote_includes() {
#[test]
fn explicitly_included_remote() {
- let view = View::new(ViewConfig {
- id: "abc".into(),
- include: vec![FilterRule::Remote(StringMatcher::Exact(
- "remote-b".to_string(),
- ))],
- exclude: vec![],
- ..Default::default()
- });
+ let config = parse_config(
+ "
+view: test
+ include remote=remote-b
+",
+ );
+
+ let view = View::new(config);
assert!(view.is_remote_explicitly_included("remote-b"));
}
#[test]
fn included_and_excluded_same_remote() {
- let view = View::new(ViewConfig {
- id: "abc".into(),
- include: vec![FilterRule::Remote(StringMatcher::Exact(
- "remote-b".to_string(),
- ))],
- exclude: vec![FilterRule::Remote(StringMatcher::Exact(
- "remote-b".to_string(),
- ))],
- ..Default::default()
- });
+ let config = parse_config(
+ "
+view: test
+ include remote=remote-b
+ exclude remote=remote-b
+",
+ );
+
+ let view = View::new(config);
assert!(!view.is_remote_explicitly_included("remote-b"));
}
#[test]
fn not_explicitly_included_remote() {
- let view = View::new(ViewConfig {
- id: "abc".into(),
- include: vec![],
- exclude: vec![],
- include_all: Some(true),
- });
+ let config = parse_config(
+ "
+view: test
+ include-all true
+",
+ );
+ let view = View::new(config);
// Assert that is not *explicitly* included
assert!(view.is_remote_explicitly_included("remote-b"));
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 4+ messages in thread