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"
|
php-literal-parser = "0.5.0"
|
||||||
sqlx = { version = "0.6.2", default-features = false, features = ["runtime-tokio-rustls", "any", "mysql", "sqlite", "postgres"], optional = true }
|
sqlx = { version = "0.6.2", default-features = false, features = ["runtime-tokio-rustls", "any", "mysql", "sqlite", "postgres"], optional = true }
|
||||||
miette = "5.5.0"
|
miette = "5.5.0"
|
||||||
|
serde_json = "1.0.87"
|
||||||
|
tracing = "0.1.37"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
sqlx = { version = "0.6.2", default-features = false, features = ["runtime-tokio-rustls", "any", "mysql", "sqlite", "postgres"] }
|
sqlx = { version = "0.6.2", default-features = false, features = ["runtime-tokio-rustls", "any", "mysql", "sqlite", "postgres"] }
|
||||||
|
|
|
||||||
10
src/lib.rs
10
src/lib.rs
|
|
@ -1,8 +1,10 @@
|
||||||
mod nc;
|
mod nc;
|
||||||
|
mod php;
|
||||||
|
|
||||||
use miette::Diagnostic;
|
use miette::Diagnostic;
|
||||||
#[cfg(feature = "redis-connect")]
|
#[cfg(feature = "redis-connect")]
|
||||||
use redis::{ConnectionAddr, ConnectionInfo};
|
use redis::{ConnectionAddr, ConnectionInfo};
|
||||||
|
use std::fmt::Debug;
|
||||||
#[cfg(feature = "redis-connect")]
|
#[cfg(feature = "redis-connect")]
|
||||||
use std::iter::once;
|
use std::iter::once;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -90,6 +92,8 @@ pub enum Error {
|
||||||
Redis,
|
Redis,
|
||||||
#[error("`overwrite.cli.url` not set`")]
|
#[error("`overwrite.cli.url` not set`")]
|
||||||
NoUrl,
|
NoUrl,
|
||||||
|
#[error("Failed to execute php to parse configuration")]
|
||||||
|
Exec,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error, Diagnostic)]
|
#[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),);
|
||||||
|
}
|
||||||
|
|
|
||||||
27
src/nc.rs
27
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| {
|
match php_literal_parser::from_str(php) {
|
||||||
Error::Php(PhpParseError {
|
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,
|
err,
|
||||||
path: path.as_ref().into(),
|
path: path.as_ref().into(),
|
||||||
})
|
}))
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge_configs(input: Vec<(PathBuf, Value)>) -> Result<Value> {
|
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))
|
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() {
|
match parsed["dbtype"].as_str() {
|
||||||
Some("mysql") => {
|
Some("mysql") => {
|
||||||
let username = parsed["dbuser"].as_str().ok_or(DbError::NoUsername)?;
|
let username = parsed["dbuser"].as_str().ok_or(DbError::NoUsername)?;
|
||||||
|
|
@ -384,16 +392,11 @@ fn test_redis_empty_password_none() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[track_caller]
|
use crate::assert_debug_equal;
|
||||||
fn assert_debug_equal<T: Debug>(a: T, b: T) {
|
use crate::php::try_exec_config;
|
||||||
assert_eq!(format!("{:?}", a), format!("{:?}", b),);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use sqlx::{any::AnyConnectOptions, postgres::PgConnectOptions};
|
use sqlx::{any::AnyConnectOptions, postgres::PgConnectOptions};
|
||||||
#[cfg(test)]
|
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn config_from_file(path: &str) -> Config {
|
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