From: Fiona Ebner <f.ebner@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH proxmox] section config: support allowing unknown section types
Date: Fri, 25 Nov 2022 16:15:35 +0100 [thread overview]
Message-ID: <20221125151536.190947-1-f.ebner@proxmox.com> (raw)
Similar to commit c9ede1c ("support unknown types in section config")
in pve-common.
Unknown sections are parsed as String-JSON String key-value pairs
without any additional checks and also written as-is.
Suggested-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
proxmox-section-config/src/lib.rs | 202 +++++++++++++++++++++++++-----
1 file changed, 174 insertions(+), 28 deletions(-)
diff --git a/proxmox-section-config/src/lib.rs b/proxmox-section-config/src/lib.rs
index d9978d1..f5bc315 100644
--- a/proxmox-section-config/src/lib.rs
+++ b/proxmox-section-config/src/lib.rs
@@ -90,11 +90,14 @@ pub struct SectionConfig {
fn(type_name: &str, section_id: &str, data: &Value) -> Result<String, Error>,
format_section_content:
fn(type_name: &str, section_id: &str, key: &str, value: &Value) -> Result<String, Error>,
+
+ allow_unknown_sections: bool,
}
enum ParseState<'a> {
BeforeHeader,
InsideSection(&'a SectionConfigPlugin, String, Value),
+ InsideUnknownSection(String, String, Value),
}
/// Interface to manipulate configuration data
@@ -238,6 +241,7 @@ impl SectionConfig {
parse_section_content: Self::default_parse_section_content,
format_section_header: Self::default_format_section_header,
format_section_content: Self::default_format_section_content,
+ allow_unknown_sections: false,
}
}
@@ -250,6 +254,7 @@ impl SectionConfig {
parse_section_content: Self::systemd_parse_section_content,
format_section_header: Self::systemd_format_section_header,
format_section_content: Self::systemd_format_section_content,
+ allow_unknown_sections: false,
}
}
@@ -277,9 +282,15 @@ impl SectionConfig {
parse_section_content,
format_section_header,
format_section_content,
+ allow_unknown_sections: false,
}
}
+ pub const fn allow_unknown_sections(mut self, allow_unknown_sections: bool) -> Self {
+ self.allow_unknown_sections = allow_unknown_sections;
+ self
+ }
+
/// Register a plugin, which defines the `Schema` for a section type.
pub fn register_plugin(&mut self, plugin: SectionConfigPlugin) {
self.plugins.insert(plugin.type_name.clone(), plugin);
@@ -324,32 +335,53 @@ impl SectionConfig {
for section_id in list {
let (type_name, section_config) = config.sections.get(section_id).unwrap();
- let plugin = self.plugins.get(type_name).unwrap();
- let id_schema = plugin.get_id_schema().unwrap_or(self.id_schema);
- if let Err(err) = id_schema.parse_simple_value(section_id) {
- bail!("syntax error in section identifier: {}", err.to_string());
- }
- if section_id.chars().any(|c| c.is_control()) {
- bail!("detected unexpected control character in section ID.");
- }
- if let Err(err) = plugin.properties.verify_json(section_config) {
- bail!("verify section '{}' failed - {}", section_id, err);
- }
+ match self.plugins.get(type_name) {
+ Some(plugin) => {
+ let id_schema = plugin.get_id_schema().unwrap_or(self.id_schema);
+ if let Err(err) = id_schema.parse_simple_value(section_id) {
+ bail!("syntax error in section identifier: {}", err.to_string());
+ }
+ if section_id.chars().any(|c| c.is_control()) {
+ bail!("detected unexpected control character in section ID.");
+ }
+ if let Err(err) = plugin.properties.verify_json(section_config) {
+ bail!("verify section '{}' failed - {}", section_id, err);
+ }
- if !raw.is_empty() {
- raw += "\n"
- }
+ if !raw.is_empty() {
+ raw += "\n"
+ }
- raw += &(self.format_section_header)(type_name, section_id, section_config)?;
+ raw += &(self.format_section_header)(type_name, section_id, section_config)?;
- for (key, value) in section_config.as_object().unwrap() {
- if let Some(id_property) = &plugin.id_property {
- if id_property == key {
- continue; // skip writing out id properties, they are in the section header
+ for (key, value) in section_config.as_object().unwrap() {
+ if let Some(id_property) = &plugin.id_property {
+ if id_property == key {
+ continue; // skip writing out id properties, they are in the section header
+ }
+ }
+ raw += &(self.format_section_content)(type_name, section_id, key, value)?;
}
}
- raw += &(self.format_section_content)(type_name, section_id, key, value)?;
+ None if self.allow_unknown_sections => {
+ if section_id.chars().any(|c| c.is_control()) {
+ bail!("detected unexpected control character in section ID.");
+ }
+
+ if !raw.is_empty() {
+ raw += "\n"
+ }
+
+ raw += &(self.format_section_header)(type_name, section_id, section_config)?;
+
+ for (key, value) in section_config.as_object().unwrap() {
+ raw += &(self.format_section_content)(type_name, section_id, key, value)?;
+ }
+ }
+ None => {
+ bail!("unknown section type '{type_name}'");
+ }
}
}
@@ -415,6 +447,12 @@ impl SectionConfig {
}
state =
ParseState::InsideSection(plugin, section_id, json!({}));
+ } else if self.allow_unknown_sections {
+ state = ParseState::InsideUnknownSection(
+ section_type,
+ section_id,
+ json!({}),
+ );
} else {
bail!("unknown section type '{}'", section_type);
}
@@ -477,18 +515,48 @@ impl SectionConfig {
bail!("syntax error (expected section properties)");
}
}
+ ParseState::InsideUnknownSection(
+ ref section_type,
+ ref mut section_id,
+ ref mut config,
+ ) => {
+ if line.trim().is_empty() {
+ // finish section
+ result.set_data(section_id, section_type, config.take())?;
+ result.record_order(section_id);
+
+ state = ParseState::BeforeHeader;
+ continue;
+ }
+ if let Some((key, value)) = (self.parse_section_content)(line) {
+ config[key] = json!(value);
+ } else {
+ bail!("syntax error (expected section properties)");
+ }
+ }
}
}
- if let ParseState::InsideSection(plugin, ref mut section_id, ref mut config) = state
- {
- // finish section
- test_required_properties(config, plugin.properties, &plugin.id_property)?;
- if let Some(id_property) = &plugin.id_property {
- config[id_property] = Value::from(section_id.clone());
+ match state {
+ ParseState::BeforeHeader => {}
+ ParseState::InsideSection(plugin, ref mut section_id, ref mut config) => {
+ // finish section
+ test_required_properties(config, plugin.properties, &plugin.id_property)?;
+ if let Some(id_property) = &plugin.id_property {
+ config[id_property] = Value::from(section_id.clone());
+ }
+ result.set_data(section_id, &plugin.type_name, config)?;
+ result.record_order(section_id);
+ }
+ ParseState::InsideUnknownSection(
+ ref section_type,
+ ref mut section_id,
+ ref mut config,
+ ) => {
+ // finish section
+ result.set_data(section_id, section_type, config)?;
+ result.record_order(section_id);
}
- result.set_data(section_id, &plugin.type_name, config)?;
- result.record_order(section_id);
}
Ok(())
@@ -960,6 +1028,84 @@ user: root@pam
assert!(config.write(filename, &res.unwrap()).is_err());
}
+#[test]
+fn test_section_config_with_unknown_section_types() {
+ let filename = "user.cfg";
+
+ const ID_SCHEMA: Schema = StringSchema::new("default id schema.")
+ .min_length(3)
+ .schema();
+ let mut config = SectionConfig::new(&ID_SCHEMA).allow_unknown_sections(true);
+
+ const PROPERTIES: [(&str, bool, &proxmox_schema::Schema); 2] = [
+ (
+ "email",
+ false,
+ &StringSchema::new("The e-mail of the user").schema(),
+ ),
+ (
+ "userid",
+ true,
+ &StringSchema::new("The id of the user (name@realm).")
+ .min_length(3)
+ .schema(),
+ ),
+ ];
+
+ const USER_PROPERTIES: ObjectSchema = ObjectSchema {
+ description: "user properties",
+ properties: &PROPERTIES,
+ additional_properties: false,
+ default_key: None,
+ };
+
+ let plugin = SectionConfigPlugin::new(
+ "user".to_string(),
+ Some("userid".to_string()),
+ &USER_PROPERTIES,
+ );
+ config.register_plugin(plugin);
+
+ let raw = r"
+
+user: root@pam
+ email root@example.com
+
+token: asdf@pbs!asdftoken
+ enable true
+ expire 0
+";
+
+ let check = |res: SectionConfigData| {
+ let (_, token_config) = res.sections.get("root@pam").unwrap();
+ assert_eq!(
+ *token_config.get("email").unwrap(),
+ json!("root@example.com")
+ );
+
+ let (token_id, token_config) = res.sections.get("asdf@pbs!asdftoken").unwrap();
+ assert_eq!(token_id, "token");
+ assert_eq!(*token_config.get("enable").unwrap(), json!("true"));
+ assert_eq!(*token_config.get("expire").unwrap(), json!("0"));
+ };
+
+ let res = config.parse(filename, raw).unwrap();
+ println!("RES: {:?}", res);
+ let written = config.write(filename, &res);
+ println!("CONFIG:\n{}", written.as_ref().unwrap());
+
+ check(res);
+
+ let res = config.parse(filename, &written.unwrap()).unwrap();
+ println!("RES second time: {:?}", res);
+
+ check(res);
+
+ let config = config.allow_unknown_sections(false);
+
+ assert!(config.parse(filename, raw).is_err());
+}
+
/// Generate ReST Documentaion for ``SectionConfig``
pub fn dump_section_config(config: &SectionConfig) -> String {
let mut res = String::new();
--
2.30.2
next reply other threads:[~2022-11-25 15:16 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-11-25 15:15 Fiona Ebner [this message]
2022-11-25 15:15 ` [pbs-devel] [PATCH proxmox-mail-forward] pbs user config: allow parsing unknown sections Fiona Ebner
2022-11-28 8:33 ` [pbs-devel] applied: " Wolfgang Bumiller
2022-11-28 8:28 ` [pbs-devel] applied: [PATCH proxmox] section config: support allowing unknown section types Wolfgang Bumiller
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20221125151536.190947-1-f.ebner@proxmox.com \
--to=f.ebner@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.