simple-config: new crate to read/write proxmox simple text config files
Copied from proxmox-backup/src/tools/config.rs Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
This commit is contained in:
parent
a17430b38f
commit
53ff71772f
@ -30,6 +30,7 @@ members = [
|
||||
"proxmox-section-config",
|
||||
"proxmox-serde",
|
||||
"proxmox-shared-memory",
|
||||
"proxmox-simple-config",
|
||||
"proxmox-sortable-macro",
|
||||
"proxmox-subscription",
|
||||
"proxmox-sys",
|
||||
|
17
proxmox-simple-config/Cargo.toml
Normal file
17
proxmox-simple-config/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "proxmox-simple-config"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
exclude.workspace = true
|
||||
description = "Simple key/value format for configuration files."
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
log = { workspace = true, optional = true }
|
||||
|
||||
proxmox-schema = { workspace = true, features = ["api-macro", "api-types"] }
|
6
proxmox-simple-config/debian/changelog
Normal file
6
proxmox-simple-config/debian/changelog
Normal file
@ -0,0 +1,6 @@
|
||||
rust-proxmox-simple-config (0.1.0-1) bookworm; urgency=medium
|
||||
|
||||
* initial packaging (copied code from proxmox-backup)
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 05 Jun 2024 12:52:06 +0200
|
||||
|
61
proxmox-simple-config/debian/control
Normal file
61
proxmox-simple-config/debian/control
Normal file
@ -0,0 +1,61 @@
|
||||
Source: rust-proxmox-simple-config
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-anyhow-1+default-dev <!nocheck>,
|
||||
librust-proxmox-schema-3+api-macro-dev (>= 3.1.1-~~) <!nocheck>,
|
||||
librust-proxmox-schema-3+api-types-dev (>= 3.1.1-~~) <!nocheck>,
|
||||
librust-proxmox-schema-3+default-dev (>= 3.1.1-~~) <!nocheck>,
|
||||
librust-serde-1+default-dev <!nocheck>,
|
||||
librust-serde-1+derive-dev <!nocheck>,
|
||||
librust-serde-json-1+default-dev <!nocheck>
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.6.2
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||
X-Cargo-Crate: proxmox-simple-config
|
||||
Rules-Requires-Root: no
|
||||
|
||||
Package: librust-proxmox-simple-config-dev
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
librust-anyhow-1+default-dev,
|
||||
librust-proxmox-schema-3+api-macro-dev (>= 3.1.1-~~),
|
||||
librust-proxmox-schema-3+api-types-dev (>= 3.1.1-~~),
|
||||
librust-proxmox-schema-3+default-dev (>= 3.1.1-~~),
|
||||
librust-serde-1+default-dev,
|
||||
librust-serde-1+derive-dev,
|
||||
librust-serde-json-1+default-dev
|
||||
Suggests:
|
||||
librust-proxmox-simple-config+log-dev (= ${binary:Version})
|
||||
Provides:
|
||||
librust-proxmox-simple-config+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-simple-config-0-dev (= ${binary:Version}),
|
||||
librust-proxmox-simple-config-0+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-simple-config-0.1-dev (= ${binary:Version}),
|
||||
librust-proxmox-simple-config-0.1+default-dev (= ${binary:Version}),
|
||||
librust-proxmox-simple-config-0.1.0-dev (= ${binary:Version}),
|
||||
librust-proxmox-simple-config-0.1.0+default-dev (= ${binary:Version})
|
||||
Description: Simple key/value format for configuration files - Rust source code
|
||||
Source code for Debianized Rust crate "proxmox-simple-config"
|
||||
|
||||
Package: librust-proxmox-simple-config+log-dev
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
librust-proxmox-simple-config-dev (= ${binary:Version}),
|
||||
librust-log-0.4+default-dev (>= 0.4.17-~~)
|
||||
Provides:
|
||||
librust-proxmox-simple-config-0+log-dev (= ${binary:Version}),
|
||||
librust-proxmox-simple-config-0.1+log-dev (= ${binary:Version}),
|
||||
librust-proxmox-simple-config-0.1.0+log-dev (= ${binary:Version})
|
||||
Description: Simple key/value format for configuration files - feature "log"
|
||||
This metapackage enables feature "log" for the Rust proxmox-simple-config
|
||||
crate, by pulling in any additional dependencies needed by that feature.
|
18
proxmox-simple-config/debian/copyright
Normal file
18
proxmox-simple-config/debian/copyright
Normal file
@ -0,0 +1,18 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
|
||||
Files:
|
||||
*
|
||||
Copyright: 2019 - 2024 Proxmox Server Solutions GmbH <support@proxmox.com>
|
||||
License: AGPL-3.0-or-later
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU Affero General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option) any
|
||||
later version.
|
||||
.
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
details.
|
||||
.
|
||||
You should have received a copy of the GNU Affero General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>.
|
7
proxmox-simple-config/debian/debcargo.toml
Normal file
7
proxmox-simple-config/debian/debcargo.toml
Normal file
@ -0,0 +1,7 @@
|
||||
overlay = "."
|
||||
crate_src_path = ".."
|
||||
maintainer = "Proxmox Support Team <support@proxmox.com>"
|
||||
|
||||
[source]
|
||||
vcs_git = "git://git.proxmox.com/git/proxmox.git"
|
||||
vcs_browser = "https://git.proxmox.com/?p=proxmox.git"
|
173
proxmox-simple-config/src/lib.rs
Normal file
173
proxmox-simple-config/src/lib.rs
Normal file
@ -0,0 +1,173 @@
|
||||
//! Our 'key: value' config format.
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use proxmox_schema::{ObjectSchemaType, Schema};
|
||||
|
||||
type Object = serde_json::Map<String, Value>;
|
||||
|
||||
fn object_schema(schema: &'static Schema) -> Result<&'static dyn ObjectSchemaType, Error> {
|
||||
Ok(match schema {
|
||||
Schema::Object(schema) => schema,
|
||||
Schema::AllOf(schema) => schema,
|
||||
_ => bail!("invalid schema for config, must be an object schema"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a full string representing a config file.
|
||||
pub fn from_str<T: for<'de> Deserialize<'de>>(
|
||||
input: &str,
|
||||
schema: &'static Schema,
|
||||
) -> Result<T, Error> {
|
||||
Ok(serde_json::from_value(value_from_str(input, schema)?)?)
|
||||
}
|
||||
|
||||
/// Parse a full string representing a config file.
|
||||
pub fn value_from_str(input: &str, schema: &'static Schema) -> Result<Value, Error> {
|
||||
let schema = object_schema(schema)?;
|
||||
|
||||
let mut config = Object::new();
|
||||
let mut lines = input.lines().enumerate().peekable();
|
||||
let mut description = String::new();
|
||||
|
||||
while let Some((_, line)) = lines.next_if(|(_, line)| line.starts_with('#')) {
|
||||
description.push_str(&line[1..]);
|
||||
description.push('\n');
|
||||
}
|
||||
|
||||
if !description.is_empty() {
|
||||
config.insert("description".to_string(), Value::String(description));
|
||||
}
|
||||
|
||||
for (lineno, line) in lines {
|
||||
let line = line.trim();
|
||||
if line.starts_with('#') || line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
parse_line(&mut config, line, schema)
|
||||
.map_err(|err| format_err!("line {}: {}", lineno, err))?;
|
||||
}
|
||||
|
||||
Ok(Value::Object(config))
|
||||
}
|
||||
|
||||
/// Parse a single `key: value` line from a config file.
|
||||
fn parse_line(
|
||||
config: &mut Object,
|
||||
line: &str,
|
||||
schema: &'static dyn ObjectSchemaType,
|
||||
) -> Result<(), Error> {
|
||||
if line.starts_with('#') || line.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let colon = line
|
||||
.find(':')
|
||||
.ok_or_else(|| format_err!("missing colon to separate key from value"))?;
|
||||
if colon == 0 {
|
||||
bail!("empty key not allowed");
|
||||
}
|
||||
|
||||
let key = &line[..colon];
|
||||
let value = line[(colon + 1)..].trim_start();
|
||||
|
||||
parse_key_value(config, key, value, schema)
|
||||
}
|
||||
|
||||
/// Lookup the key in the schema, parse the value and insert it into the config object.
|
||||
fn parse_key_value(
|
||||
config: &mut Object,
|
||||
key: &str,
|
||||
value: &str,
|
||||
schema: &'static dyn ObjectSchemaType,
|
||||
) -> Result<(), Error> {
|
||||
let schema = match schema.lookup(key) {
|
||||
Some((_optional, schema)) => Some(schema),
|
||||
None if schema.additional_properties() => None,
|
||||
None => bail!(
|
||||
"invalid key '{}' and schema does not allow additional properties",
|
||||
key
|
||||
),
|
||||
};
|
||||
|
||||
let value = parse_value(value, schema)?;
|
||||
config.insert(key.to_owned(), value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// For this we can just reuse the schema's "parse_simple_value".
|
||||
///
|
||||
/// "Additional" properties (`None` schema) will simply become strings.
|
||||
///
|
||||
/// Note that this does not handle Object or Array types at all, so if we want to support them
|
||||
/// natively without going over a `String` type, we can add this here.
|
||||
fn parse_value(value: &str, schema: Option<&'static Schema>) -> Result<Value, Error> {
|
||||
match schema {
|
||||
None => Ok(Value::String(value.to_owned())),
|
||||
Some(schema) => schema.parse_simple_value(value),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a string as a property string into a deserializable type. This is just a short wrapper
|
||||
/// around deserializing the s
|
||||
pub fn from_property_string<T>(input: &str, schema: &'static Schema) -> Result<T, Error>
|
||||
where
|
||||
T: for<'de> Deserialize<'de>,
|
||||
{
|
||||
Ok(serde_json::from_value(
|
||||
schema.parse_property_string(input)?,
|
||||
)?)
|
||||
}
|
||||
|
||||
/// Serialize a data structure using a 'key: value' config file format.
|
||||
pub fn to_bytes<T: Serialize>(value: &T, schema: &'static Schema) -> Result<Vec<u8>, Error> {
|
||||
value_to_bytes(&serde_json::to_value(value)?, schema)
|
||||
}
|
||||
|
||||
/// Serialize a json value using a 'key: value' config file format.
|
||||
pub fn value_to_bytes(value: &Value, schema: &'static Schema) -> Result<Vec<u8>, Error> {
|
||||
let schema = object_schema(schema)?;
|
||||
|
||||
schema.verify_json(value)?;
|
||||
|
||||
let object = value
|
||||
.as_object()
|
||||
.ok_or_else(|| format_err!("value must be an object"))?;
|
||||
|
||||
let mut out = Vec::new();
|
||||
object_to_writer(&mut out, object)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Note: the object must have already been verified at this point.
|
||||
fn object_to_writer(output: &mut dyn Write, object: &Object) -> Result<(), Error> {
|
||||
// special key `description` for multi-line notes, must be written before everything else
|
||||
if let Some(Value::String(description)) = object.get("description") {
|
||||
for lines in description.lines() {
|
||||
writeln!(output, "#{}", lines)?;
|
||||
}
|
||||
}
|
||||
|
||||
for (key, value) in object.iter() {
|
||||
match value {
|
||||
_ if key == "description" => continue, // skip description as we handle it above
|
||||
Value::Null => continue, // delete this entry
|
||||
Value::Bool(v) => writeln!(output, "{}: {}", key, v)?,
|
||||
Value::String(v) => {
|
||||
if v.as_bytes().contains(&b'\n') {
|
||||
bail!("value for {} contains newlines", key);
|
||||
}
|
||||
writeln!(output, "{}: {}", key, v)?
|
||||
}
|
||||
Value::Number(v) => writeln!(output, "{}: {}", key, v)?,
|
||||
Value::Array(_) => bail!("arrays are not supported in config files"),
|
||||
Value::Object(_) => bail!("complex objects are not supported in config files"),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user