* [pdm-devel] [PATCH datacenter-manager 0/3] views: preparations for regex/glob, include-all param
@ 2025-11-17 14:11 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
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Lukas Wagner @ 2025-11-17 14:11 UTC (permalink / raw)
To: pdm-devel
As brought up in:
https://lore.proxmox.com/pdm-devel/20251113121644.236005-1-l.wagner@proxmox.com/T/#m1a2463844e8d833457135cbdd87bb0de707ffc08
Lukas Wagner (3):
pdm-api-types: views: preparations for future glob/regex support
views: add 'include-all' param; change semantics when there are no
includes
views: tests: use full section-config format for test cases
lib/pdm-api-types/src/views.rs | 189 ++++++++++++------
server/src/views/mod.rs | 82 ++++----
server/src/views/tests.rs | 339 +++++++++++++++++++--------------
3 files changed, 377 insertions(+), 233 deletions(-)
--
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 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
end of thread, other threads:[~2025-11-17 14:12 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [pdm-devel] [PATCH datacenter-manager 3/3] views: tests: use full section-config format for test cases Lukas Wagner
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.