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:
Lucy C
2021-10-14 14:51:47 -06:00
committed by Aiden McClelland
parent 1ed7396dd1
commit effcd5ea57
5 changed files with 210 additions and 61 deletions

View File

@@ -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,

View File

@@ -91,6 +91,7 @@ pub fn server() -> Result<(), RpcError> {
control::stop,
logs::logs,
properties::properties,
dependencies::dependency
))]
pub fn package() -> Result<(), RpcError> {
Ok(())

View File

@@ -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)),
}
}

View File

@@ -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 + \"!\"")

View File

@@ -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) => {