mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
Feature/dry auto configure (#584)
* rename variables for clarity * return altered dep config * add utils system image, move compat into system-images * rename variables for clarity * sync integration and add debug instrumentation * debugging * add trace instrumentation * fix compilation for instrumentation * fix potential deadlocking behavior * fix import * fix dep check response return * hook back up to rpc, was overwritten in rebase * fix package command * get proper package config * testing dep config * version/volume for dep * vars * compat debugs * clean up * remove tar Co-authored-by: Chris Guida <chrisguida@gmail.com>
This commit is contained in:
@@ -1,23 +1,34 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::util::display_none;
|
||||
use color_eyre::eyre::eyre;
|
||||
use emver::VersionRange;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::FutureExt;
|
||||
use patch_db::{DbHandle, DiffPatch, HasModel, Map, MapModel};
|
||||
use patch_db::{DbHandle, HasModel, Map, MapModel, PatchDbHandle};
|
||||
use rpc_toolkit::command;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::action::{ActionImplementation, NoOutput};
|
||||
use crate::config::Config;
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::CurrentDependencyInfo;
|
||||
use crate::error::ResultExt;
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::status::health_check::{HealthCheckId, HealthCheckResult};
|
||||
use crate::status::{MainStatus, Status};
|
||||
use crate::util::display_serializable;
|
||||
use crate::util::Version;
|
||||
use crate::volume::Volumes;
|
||||
use crate::Error;
|
||||
|
||||
#[command(subcommands(configure))]
|
||||
pub fn dependency() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, thiserror::Error, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[serde(tag = "type")]
|
||||
@@ -78,6 +89,7 @@ impl DependencyError {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[instrument(skip(ctx, db))]
|
||||
pub fn try_heal<'a, Db: DbHandle>(
|
||||
self,
|
||||
ctx: &'a RpcContext,
|
||||
@@ -166,7 +178,7 @@ impl DependencyError {
|
||||
Config::default()
|
||||
};
|
||||
if let Some(cfg_req) = &info.config {
|
||||
if let Err(e) = cfg_req
|
||||
if let Err(error) = cfg_req
|
||||
.check(
|
||||
ctx,
|
||||
id,
|
||||
@@ -174,15 +186,9 @@ impl DependencyError {
|
||||
&dependent_manifest.volumes,
|
||||
&dependency_config,
|
||||
)
|
||||
.await
|
||||
.await?
|
||||
{
|
||||
if e.kind == crate::ErrorKind::ConfigRulesViolation {
|
||||
return Ok(Some(DependencyError::ConfigUnsatisfied {
|
||||
error: format!("{}", e),
|
||||
}));
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
return Ok(Some(DependencyError::ConfigUnsatisfied { error }));
|
||||
}
|
||||
}
|
||||
DependencyError::NotRunning
|
||||
@@ -440,6 +446,146 @@ impl DependencyConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[command(
|
||||
subcommands(self(configure_impl(async)), configure_dry),
|
||||
display(display_none)
|
||||
)]
|
||||
pub async fn configure(
|
||||
#[arg(rename = "dependent-id")] dependent_id: PackageId,
|
||||
#[arg(rename = "dependency-id")] dependency_id: PackageId,
|
||||
) -> Result<(PackageId, PackageId), Error> {
|
||||
Ok((dependent_id, dependency_id))
|
||||
}
|
||||
|
||||
pub async fn configure_impl(
|
||||
ctx: RpcContext,
|
||||
(pkg_id, dep_id): (PackageId, PackageId),
|
||||
) -> Result<(), Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let new_config = configure_logic(ctx.clone(), &mut db, (pkg_id, dep_id.clone())).await?;
|
||||
Ok(crate::config::configure(
|
||||
&ctx,
|
||||
&mut db,
|
||||
&dep_id,
|
||||
Some(new_config),
|
||||
&Some(Duration::from_secs(3)),
|
||||
false,
|
||||
&mut BTreeMap::new(),
|
||||
&mut BTreeMap::new(),
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[command(rename = "dry", display(display_serializable))]
|
||||
#[instrument(skip(ctx))]
|
||||
pub async fn configure_dry(
|
||||
#[context] ctx: RpcContext,
|
||||
#[parent_data] (pkg_id, dependency_id): (PackageId, PackageId),
|
||||
) -> Result<Config, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
configure_logic(ctx, &mut db, (pkg_id, dependency_id)).await
|
||||
}
|
||||
|
||||
pub async fn configure_logic(
|
||||
ctx: RpcContext,
|
||||
db: &mut PatchDbHandle,
|
||||
(pkg_id, dependency_id): (PackageId, PackageId),
|
||||
) -> Result<Config, Error> {
|
||||
let pkg_model = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&pkg_id)
|
||||
.and_then(|m| m.installed())
|
||||
.expect(db)
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::NotFound)?;
|
||||
let pkg_version = pkg_model.clone().manifest().version().get(db, true).await?;
|
||||
let pkg_volumes = pkg_model.clone().manifest().volumes().get(db, true).await?;
|
||||
let dependency_model = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&dependency_id)
|
||||
.and_then(|m| m.installed())
|
||||
.expect(db)
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::NotFound)?;
|
||||
let dependency_config_action = dependency_model
|
||||
.clone()
|
||||
.manifest()
|
||||
.config()
|
||||
.get(db, true)
|
||||
.await?
|
||||
.to_owned()
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("{} has no config", dependency_id),
|
||||
crate::ErrorKind::NotFound,
|
||||
)
|
||||
})?;
|
||||
let dependency_version = dependency_model
|
||||
.clone()
|
||||
.manifest()
|
||||
.version()
|
||||
.get(db, true)
|
||||
.await?;
|
||||
let dependency_volumes = dependency_model
|
||||
.clone()
|
||||
.manifest()
|
||||
.volumes()
|
||||
.get(db, true)
|
||||
.await?;
|
||||
let dependencies = pkg_model
|
||||
.clone()
|
||||
.manifest()
|
||||
.dependencies()
|
||||
.get(db, true)
|
||||
.await?;
|
||||
|
||||
let dependency = dependencies
|
||||
.get(&dependency_id)
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!(
|
||||
"dependency for {} not found in the manifest for {}",
|
||||
dependency_id,
|
||||
pkg_id
|
||||
),
|
||||
crate::ErrorKind::NotFound,
|
||||
)
|
||||
})?
|
||||
.config
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!(
|
||||
"dependency config for {} not found on {}",
|
||||
dependency_id,
|
||||
pkg_id
|
||||
),
|
||||
crate::ErrorKind::NotFound,
|
||||
)
|
||||
})?;
|
||||
let config: Config = dependency_config_action
|
||||
.get(
|
||||
&ctx,
|
||||
&dependency_id,
|
||||
&*dependency_version,
|
||||
&*dependency_volumes,
|
||||
)
|
||||
.await?
|
||||
.config
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("no config get action found for {}", dependency_id),
|
||||
crate::ErrorKind::NotFound,
|
||||
)
|
||||
})?;
|
||||
Ok(dependency
|
||||
.auto_configure
|
||||
.sandboxed(&ctx, &pkg_id, &pkg_version, &pkg_volumes, Some(config))
|
||||
.await?
|
||||
.map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::AutoConfigure))?)
|
||||
}
|
||||
|
||||
#[instrument(skip(db, current_dependencies))]
|
||||
pub async fn update_current_dependents<
|
||||
'a,
|
||||
Db: DbHandle,
|
||||
@@ -542,6 +688,7 @@ pub async fn break_all_dependents_transitive<'a, Db: DbHandle>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(db))]
|
||||
pub fn break_transitive<'a, Db: DbHandle>(
|
||||
db: &'a mut Db,
|
||||
id: &'a PackageId,
|
||||
@@ -609,6 +756,7 @@ pub fn break_transitive<'a, Db: DbHandle>(
|
||||
.boxed()
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, db))]
|
||||
pub async fn heal_all_dependents_transitive<'a, Db: DbHandle>(
|
||||
ctx: &'a RpcContext,
|
||||
db: &'a mut Db,
|
||||
@@ -629,6 +777,7 @@ pub async fn heal_all_dependents_transitive<'a, Db: DbHandle>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, db))]
|
||||
pub fn heal_transitive<'a, Db: DbHandle>(
|
||||
ctx: &'a RpcContext,
|
||||
db: &'a mut Db,
|
||||
|
||||
@@ -91,6 +91,7 @@ pub fn server() -> Result<(), RpcError> {
|
||||
control::stop,
|
||||
logs::logs,
|
||||
properties::properties,
|
||||
dependencies::dependency
|
||||
))]
|
||||
pub fn package() -> Result<(), RpcError> {
|
||||
Ok(())
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::path::Path;
|
||||
|
||||
use beau_collector::BeauCollector;
|
||||
use embassy::config::action::SetResult;
|
||||
use embassy::config::{Config, spec};
|
||||
use embassy::config::{spec, Config};
|
||||
use linear_map::LinearMap;
|
||||
|
||||
pub mod rules;
|
||||
@@ -28,7 +28,10 @@ pub fn validate_configuration(
|
||||
match rule_check {
|
||||
Ok(_) => {
|
||||
// create temp config file
|
||||
serde_yaml::to_writer(std::fs::File::create(config_path.with_extension("tmp"))?, &config)?;
|
||||
serde_yaml::to_writer(
|
||||
std::fs::File::create(config_path.with_extension("tmp"))?,
|
||||
&config,
|
||||
)?;
|
||||
std::fs::rename(config_path.with_extension("tmp"), config_path)?;
|
||||
// return set result
|
||||
Ok(SetResult {
|
||||
@@ -37,7 +40,7 @@ pub fn validate_configuration(
|
||||
signal: Some(nix::sys::signal::SIGTERM),
|
||||
})
|
||||
}
|
||||
Err(e) => Err(anyhow!("{}", e))
|
||||
Err(e) => Err(anyhow!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,28 +61,28 @@ pub fn validate_dependency_configuration(
|
||||
.bcollect::<Vec<_>>();
|
||||
match rule_check {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(anyhow!("{}", e))
|
||||
Err(e) => Err(anyhow!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_dependency_configuration(
|
||||
name: &str,
|
||||
package_id: &str,
|
||||
config: Config,
|
||||
parent_name: &str,
|
||||
mut parent_config: Config,
|
||||
dependency_id: &str,
|
||||
mut dep_config: Config,
|
||||
rules_path: &Path,
|
||||
) -> Result<Config, anyhow::Error> {
|
||||
let rules: Vec<ConfigRuleEntryWithSuggestions> =
|
||||
serde_yaml::from_reader(std::fs::File::open(rules_path)?)?;
|
||||
let mut cfgs = LinearMap::new();
|
||||
cfgs.insert(parent_name, Cow::Owned(parent_config.clone()));
|
||||
cfgs.insert(name, Cow::Owned(config.clone()));
|
||||
cfgs.insert(dependency_id, Cow::Owned(dep_config.clone()));
|
||||
cfgs.insert(package_id, Cow::Owned(config.clone()));
|
||||
let rule_check = rules
|
||||
.into_iter()
|
||||
.map(|r| r.apply(parent_name, &mut parent_config, &mut cfgs))
|
||||
.map(|r| r.apply(dependency_id, &mut dep_config, &mut cfgs))
|
||||
.bcollect::<Vec<_>>();
|
||||
match rule_check {
|
||||
Ok(_) => Ok(parent_config),
|
||||
Ok(_) => Ok(dep_config),
|
||||
Err(e) => Err(anyhow!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ use pest::Parser;
|
||||
use rand::SeedableRng;
|
||||
use serde_json::Value;
|
||||
|
||||
use embassy::config::Config;
|
||||
use embassy::config::util::STATIC_NULL;
|
||||
use embassy::config::Config;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[grammar = "config/rule_parser.pest"]
|
||||
@@ -114,7 +114,7 @@ impl ConfigRuleEntry {
|
||||
cfgs: &LinearMap<&str, Cow<Config>>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
if !(self.rule.compiled)(cfg, cfgs) {
|
||||
return Err(anyhow::anyhow!("{}", self.description))
|
||||
return Err(anyhow::anyhow!("{}", self.description));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -514,9 +514,8 @@ fn compile_var_rec(mut ident: Pairs<Rule>) -> Option<Accessor> {
|
||||
Rule::sub_ident_regular_expr => {
|
||||
let idx = compile_str_expr(idx.into_inner().next().unwrap().into_inner());
|
||||
Box::new(move |v, dep_cfg| match v {
|
||||
Value::Object(o) => idx(&Config::default(), dep_cfg).map(|idx| {
|
||||
idx.and_then(|idx| o.get(&idx)).unwrap_or(&STATIC_NULL)
|
||||
}),
|
||||
Value::Object(o) => idx(&Config::default(), dep_cfg)
|
||||
.map(|idx| idx.and_then(|idx| o.get(&idx)).unwrap_or(&STATIC_NULL)),
|
||||
_ => VarRes::Exactly(&STATIC_NULL),
|
||||
})
|
||||
}
|
||||
@@ -605,16 +604,15 @@ fn compile_var_mut_rec(mut ident: Pairs<Rule>) -> Result<Option<AccessorMut>, fa
|
||||
predicate(&cfg, cfgs)
|
||||
})
|
||||
.next(),
|
||||
Value::Object(o) => {
|
||||
o.iter_mut()
|
||||
.map(|(_, item)| item)
|
||||
.filter(|item| {
|
||||
let mut cfg = Config::default();
|
||||
cfg.insert(item_var.clone(), (*item).clone());
|
||||
predicate(&cfg, cfgs)
|
||||
})
|
||||
.next()
|
||||
}
|
||||
Value::Object(o) => o
|
||||
.iter_mut()
|
||||
.map(|(_, item)| item)
|
||||
.filter(|item| {
|
||||
let mut cfg = Config::default();
|
||||
cfg.insert(item_var.clone(), (*item).clone());
|
||||
predicate(&cfg, cfgs)
|
||||
})
|
||||
.next(),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
@@ -631,16 +629,15 @@ fn compile_var_mut_rec(mut ident: Pairs<Rule>) -> Result<Option<AccessorMut>, fa
|
||||
predicate(&cfg, cfgs)
|
||||
})
|
||||
.next_back(),
|
||||
Value::Object(o) => {
|
||||
o.iter_mut()
|
||||
.map(|(_, item)| item)
|
||||
.filter(|item| {
|
||||
let mut cfg = Config::default();
|
||||
cfg.insert(item_var.clone(), (*item).clone());
|
||||
predicate(&cfg, cfgs)
|
||||
})
|
||||
.next_back()
|
||||
}
|
||||
Value::Object(o) => o
|
||||
.iter_mut()
|
||||
.map(|(_, item)| item)
|
||||
.filter(|item| {
|
||||
let mut cfg = Config::default();
|
||||
cfg.insert(item_var.clone(), (*item).clone());
|
||||
predicate(&cfg, cfgs)
|
||||
})
|
||||
.next_back(),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
@@ -780,7 +777,7 @@ fn compile_num_var(var: Pairs<Rule>) -> CompiledExpr<VarRes<f64>> {
|
||||
Value::Number(n) => n.as_f64().unwrap(),
|
||||
Value::String(s) => match s.parse() {
|
||||
Ok(n) => n,
|
||||
Err(_) => panic!("string cannot be parsed as an f64")
|
||||
Err(_) => panic!("string cannot be parsed as an f64"),
|
||||
},
|
||||
Value::Bool(b) => {
|
||||
if b {
|
||||
@@ -1045,11 +1042,12 @@ fn compile_value_expr(mut pairs: Pairs<Rule>) -> CompiledExpr<VarRes<Value>> {
|
||||
}
|
||||
Rule::num_expr => {
|
||||
let expr = compile_num_expr(expr.into_inner());
|
||||
Box::new(move |cfg, cfgs| expr(cfg, cfgs).map(|n| match
|
||||
serde_json::Number::from_f64(n) {
|
||||
Box::new(move |cfg, cfgs| {
|
||||
expr(cfg, cfgs).map(|n| match serde_json::Number::from_f64(n) {
|
||||
Some(a) => Value::Number(a),
|
||||
None => panic!("cannot coerce f64 into numberc type")
|
||||
}))
|
||||
None => panic!("cannot coerce f64 into numberc type"),
|
||||
})
|
||||
})
|
||||
}
|
||||
Rule::bool_expr => {
|
||||
let expr = compile_bool_expr(expr.into_inner());
|
||||
@@ -1244,10 +1242,8 @@ mod test {
|
||||
let mut dependent_cfg = Config::default();
|
||||
let mut dependency_cfg = Config::default();
|
||||
let mut cfgs = LinearMap::new();
|
||||
dependent_cfg
|
||||
.insert("foo".to_owned(), Value::String("bar".to_owned()));
|
||||
dependency_cfg
|
||||
.insert("foo".to_owned(), Value::String("bar!".to_owned()));
|
||||
dependent_cfg.insert("foo".to_owned(), Value::String("bar".to_owned()));
|
||||
dependency_cfg.insert("foo".to_owned(), Value::String("bar!".to_owned()));
|
||||
cfgs.insert("my-dependent", Cow::Borrowed(&dependent_cfg));
|
||||
cfgs.insert("my-dependency", Cow::Borrowed(&dependency_cfg));
|
||||
assert!((compile("'foo = '[my-dependent].foo + \"!\"")
|
||||
|
||||
@@ -238,7 +238,7 @@ fn inner_main() -> Result<(), anyhow::Error> {
|
||||
}
|
||||
}
|
||||
("auto-configure", Some(sub_m)) => {
|
||||
let parent_config = serde_yaml::from_reader(stdin())?;
|
||||
let dep_config = serde_yaml::from_reader(stdin())?;
|
||||
let config = serde_yaml::from_reader(
|
||||
File::open(
|
||||
Path::new(sub_m.value_of("mountpoint").unwrap()).join("start9/config.yaml"),
|
||||
@@ -246,13 +246,13 @@ fn inner_main() -> Result<(), anyhow::Error> {
|
||||
.unwrap(),
|
||||
)?;
|
||||
let rules_path = Path::new(sub_m.value_of("assets").unwrap());
|
||||
let name = sub_m.value_of("dependent_package_id").unwrap();
|
||||
let parent_name = sub_m.value_of("dependency_package_id").unwrap();
|
||||
let package_id = sub_m.value_of("dependent_package_id").unwrap();
|
||||
let dependency_id = sub_m.value_of("dependency_package_id").unwrap();
|
||||
match apply_dependency_configuration(
|
||||
name,
|
||||
package_id,
|
||||
config,
|
||||
parent_name,
|
||||
parent_config,
|
||||
dependency_id,
|
||||
dep_config,
|
||||
rules_path,
|
||||
) {
|
||||
Ok(a) => {
|
||||
|
||||
Reference in New Issue
Block a user