mirror of
https://codeberg.org/icewind/nextcloud-config-parser.git
synced 2026-06-03 16:44:09 +02:00
fallback to having php parse the config file and exporting it as json
This commit is contained in:
parent
8b6e3d4061
commit
f9486f3e70
5 changed files with 139 additions and 15 deletions
|
|
@ -15,6 +15,8 @@ thiserror = "1.0.38"
|
|||
php-literal-parser = "0.5.0"
|
||||
sqlx = { version = "0.6.2", default-features = false, features = ["runtime-tokio-rustls", "any", "mysql", "sqlite", "postgres"], optional = true }
|
||||
miette = "5.5.0"
|
||||
serde_json = "1.0.87"
|
||||
tracing = "0.1.37"
|
||||
|
||||
[dev-dependencies]
|
||||
sqlx = { version = "0.6.2", default-features = false, features = ["runtime-tokio-rustls", "any", "mysql", "sqlite", "postgres"] }
|
||||
|
|
@ -22,4 +24,4 @@ miette = { version = "5.5.0", features = ["fancy"] }
|
|||
|
||||
[features]
|
||||
db-sqlx = ["sqlx"]
|
||||
redis-connect = ["redis"]
|
||||
redis-connect = ["redis"]
|
||||
10
src/lib.rs
10
src/lib.rs
|
|
@ -1,8 +1,10 @@
|
|||
mod nc;
|
||||
mod php;
|
||||
|
||||
use miette::Diagnostic;
|
||||
#[cfg(feature = "redis-connect")]
|
||||
use redis::{ConnectionAddr, ConnectionInfo};
|
||||
use std::fmt::Debug;
|
||||
#[cfg(feature = "redis-connect")]
|
||||
use std::iter::once;
|
||||
use std::path::PathBuf;
|
||||
|
|
@ -90,6 +92,8 @@ pub enum Error {
|
|||
Redis,
|
||||
#[error("`overwrite.cli.url` not set`")]
|
||||
NoUrl,
|
||||
#[error("Failed to execute php to parse configuration")]
|
||||
Exec,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
|
|
@ -234,3 +238,9 @@ impl From<Database> for sqlx::any::AnyConnectOptions {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[track_caller]
|
||||
pub fn assert_debug_equal<T: Debug>(a: T, b: T) {
|
||||
assert_eq!(format!("{:?}", a), format!("{:?}", b),);
|
||||
}
|
||||
|
|
|
|||
31
src/nc.rs
31
src/nc.rs
|
|
@ -65,12 +65,20 @@ fn parse_php(path: impl AsRef<Path>) -> Result<Value> {
|
|||
)));
|
||||
}
|
||||
};
|
||||
php_literal_parser::from_str(php).map_err(|err| {
|
||||
Error::Php(PhpParseError {
|
||||
err,
|
||||
path: path.as_ref().into(),
|
||||
})
|
||||
})
|
||||
match php_literal_parser::from_str(php) {
|
||||
Ok(config) => Ok(config),
|
||||
Err(err) => {
|
||||
// attempt parsing the config file with php
|
||||
if let Ok(config) = try_exec_config(&path) {
|
||||
Ok(config)
|
||||
} else {
|
||||
Err(Error::Php(PhpParseError {
|
||||
err,
|
||||
path: path.as_ref().into(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_configs(input: Vec<(PathBuf, Value)>) -> Result<Value> {
|
||||
|
|
@ -131,7 +139,7 @@ pub fn parse_glob(path: impl AsRef<Path>) -> Result<Config> {
|
|||
parse_files(glob_config_files(path))
|
||||
}
|
||||
|
||||
fn parse_db_options(parsed: &Value) -> Result<Database> {
|
||||
pub(crate) fn parse_db_options(parsed: &Value) -> Result<Database> {
|
||||
match parsed["dbtype"].as_str() {
|
||||
Some("mysql") => {
|
||||
let username = parsed["dbuser"].as_str().ok_or(DbError::NoUsername)?;
|
||||
|
|
@ -384,16 +392,11 @@ fn test_redis_empty_password_none() {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[track_caller]
|
||||
fn assert_debug_equal<T: Debug>(a: T, b: T) {
|
||||
assert_eq!(format!("{:?}", a), format!("{:?}", b),);
|
||||
}
|
||||
|
||||
use crate::assert_debug_equal;
|
||||
use crate::php::try_exec_config;
|
||||
#[cfg(test)]
|
||||
#[allow(unused_imports)]
|
||||
use sqlx::{any::AnyConnectOptions, postgres::PgConnectOptions};
|
||||
#[cfg(test)]
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[cfg(test)]
|
||||
fn config_from_file(path: &str) -> Config {
|
||||
|
|
|
|||
92
src/php.rs
Normal file
92
src/php.rs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
use crate::Error;
|
||||
use php_literal_parser::{Key, Value};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
fn from_json(json: serde_json::Value) -> Value {
|
||||
match json {
|
||||
serde_json::Value::Null => Value::Null,
|
||||
serde_json::Value::Bool(b) => Value::Bool(b),
|
||||
serde_json::Value::Number(n) => {
|
||||
if let Some(f) = n.as_f64() {
|
||||
Value::Float(f)
|
||||
} else if let Some(i) = n.as_i64() {
|
||||
Value::Int(i)
|
||||
} else {
|
||||
// > i64::MAX
|
||||
Value::Null
|
||||
}
|
||||
}
|
||||
serde_json::Value::String(s) => Value::String(s),
|
||||
serde_json::Value::Array(a) => Value::Array(
|
||||
a.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| (Key::Int(i as i64), from_json(v)))
|
||||
.collect(),
|
||||
),
|
||||
serde_json::Value::Object(o) => Value::Array(
|
||||
o.into_iter()
|
||||
.map(|(i, v)| (Key::String(i), from_json(v)))
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(path = %path.as_ref().display()))]
|
||||
pub fn try_exec_config<P: AsRef<Path>>(path: P) -> Result<Value, Error> {
|
||||
debug!("Attempting executing the config file php as fallback");
|
||||
let path = path.as_ref();
|
||||
let cmd = Command::new("php")
|
||||
.arg("-r")
|
||||
.arg(format!(
|
||||
r#"
|
||||
include "{}";
|
||||
echo json_encode($CONFIG);
|
||||
"#,
|
||||
path.display()
|
||||
))
|
||||
.output()
|
||||
.map_err(|e| {
|
||||
warn!(
|
||||
config_file = %path.display(),
|
||||
error = %e,
|
||||
"error while executing config file with php"
|
||||
);
|
||||
Error::Exec
|
||||
})?;
|
||||
let stdout = cmd.stdout;
|
||||
let json: serde_json::Value = serde_json::from_slice(&stdout).map_err(|_e| {
|
||||
warn!(
|
||||
config_file = %path.display(),
|
||||
json = ?std::str::from_utf8(&stdout),
|
||||
"php returned invalid json"
|
||||
);
|
||||
Error::Exec
|
||||
})?;
|
||||
Ok(from_json(json))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::nc::parse_db_options;
|
||||
#[cfg(test)]
|
||||
use crate::{assert_debug_equal, Database, DbConnect, SslOptions};
|
||||
|
||||
#[test]
|
||||
fn test_parse_redis_socket() {
|
||||
let database =
|
||||
parse_db_options(&try_exec_config("tests/configs/non_literal.php").unwrap()).unwrap();
|
||||
assert_debug_equal(
|
||||
&Database::MySql {
|
||||
database: "foo_db".to_string(),
|
||||
username: "nextcloud".to_string(),
|
||||
password: "secret".to_string(),
|
||||
connect: DbConnect::Tcp {
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 3306,
|
||||
},
|
||||
ssl_options: SslOptions::Disabled,
|
||||
},
|
||||
&database,
|
||||
);
|
||||
}
|
||||
17
tests/configs/non_literal.php
Normal file
17
tests/configs/non_literal.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
function get_foo(): string { return 'foo'; }
|
||||
|
||||
$CONFIG = [
|
||||
'overwrite.cli.url' => 'https://cloud.example.com',
|
||||
'dbtype' => 'mysql',
|
||||
'dbname' => get_foo() . '_db',
|
||||
'dbhost' => '127.0.0.1',
|
||||
'dbport' => '',
|
||||
'dbtableprefix' => 'oc_',
|
||||
'dbuser' => 'nextcloud',
|
||||
'dbpassword' => 'secret',
|
||||
'redis' => [
|
||||
'host' => 'localhost'
|
||||
]
|
||||
];
|
||||
Loading…
Add table
Add a link
Reference in a new issue