appmgr 0.3.0 rewrite pt 1

appmgr: split bins

update cargo.toml and .gitignore

context

appmgr: refactor error module

appmgr: context

begin new s9pk format

appmgr: add fields to manifest

appmgr: start action abstraction

appmgr: volume abstraction

appmgr: improved volumes

appmgr: install wip

appmgr: health daemon

appmgr: health checks

appmgr: wip

config get

appmgr: secret store

wip

appmgr: config rewritten

appmgr: delete non-reusable code

appmgr: wip

appmgr: please the borrow-checker

appmgr: technically runs now

appmgr: cli

appmgr: clean up cli

appmgr: rpc-toolkit in action

appmgr: wrap up config

appmgr: account for updates during install

appmgr: fix: #308

appmgr: impl Display for Version

appmgr: cleanup

appmgr: set dependents on install

appmgr: dependency health checks
This commit is contained in:
Aiden McClelland
2021-04-08 11:16:25 -06:00
committed by Aiden McClelland
parent 5741cf084f
commit 8954e3e338
84 changed files with 7510 additions and 9950 deletions

135
appmgr/src/context/cli.rs Normal file
View File

@@ -0,0 +1,135 @@
use std::fs::File;
use std::net::IpAddr;
use std::path::Path;
use std::sync::Arc;
use clap::ArgMatches;
use reqwest::Proxy;
use rpc_toolkit::reqwest::{Client, Url};
use rpc_toolkit::url::Host;
use rpc_toolkit::Context;
use serde::Deserialize;
use super::rpc::RpcContextConfig;
use crate::ResultExt;
#[derive(Debug, Default, Deserialize)]
pub struct CliContextConfig {
#[serde(deserialize_with = "deserialize_host")]
pub host: Option<Host>,
pub port: Option<u16>,
#[serde(deserialize_with = "crate::util::deserialize_from_str_opt")]
pub proxy: Option<Url>,
#[serde(flatten)]
pub server_config: RpcContextConfig,
}
#[derive(Debug)]
pub struct CliContextSeed {
pub host: Host,
pub port: u16,
pub client: Client,
}
#[derive(Debug, Clone)]
pub struct CliContext(Arc<CliContextSeed>);
impl CliContext {
pub fn init(matches: &ArgMatches) -> Result<Self, crate::Error> {
let cfg_path = Path::new(crate::CONFIG_PATH);
let mut base = if cfg_path.exists() {
serde_yaml::from_reader(
File::open(cfg_path)
.with_ctx(|_| (crate::ErrorKind::Filesystem, cfg_path.display().to_string()))?,
)
.with_kind(crate::ErrorKind::Deserialization)?
} else {
CliContextConfig::default()
};
if let Some(bind) = base.server_config.bind {
if base.host.is_none() {
base.host = Some(match bind.ip() {
IpAddr::V4(a) => Host::Ipv4(a),
IpAddr::V6(a) => Host::Ipv6(a),
});
}
if base.port.is_none() {
base.port = Some(bind.port())
}
}
if let Some(host) = matches.value_of("host") {
base.host = Some(Host::parse(host).with_kind(crate::ErrorKind::ParseUrl)?);
}
if let Some(port) = matches.value_of("port") {
base.port = Some(port.parse()?);
}
if let Some(proxy) = matches.value_of("proxy") {
base.proxy = Some(proxy.parse()?);
}
Ok(CliContext(Arc::new(CliContextSeed {
host: base.host.unwrap_or(Host::Ipv4([127, 0, 0, 1].into())),
port: base.port.unwrap_or(5959),
client: if let Some(proxy) = base.proxy {
Client::builder()
.proxy(Proxy::all(proxy).with_kind(crate::ErrorKind::ParseUrl)?)
.build()
.expect("cannot fail")
} else {
Client::new()
},
})))
}
}
impl Context for CliContext {
fn host(&self) -> Host<&str> {
match &self.0.host {
Host::Domain(a) => Host::Domain(a.as_str()),
Host::Ipv4(a) => Host::Ipv4(*a),
Host::Ipv6(a) => Host::Ipv6(*a),
}
}
fn port(&self) -> u16 {
self.0.port
}
fn client(&self) -> &Client {
&self.0.client
}
}
fn deserialize_host<'de, D: serde::de::Deserializer<'de>>(
deserializer: D,
) -> Result<Option<Host>, D::Error> {
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = Option<Host>;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "a parsable string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Host::parse(v)
.map(Some)
.map_err(|e| serde::de::Error::custom(e))
}
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_str(Visitor)
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(None)
}
fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(None)
}
}
deserializer.deserialize_any(Visitor)
}

109
appmgr/src/context/mod.rs Normal file
View File

@@ -0,0 +1,109 @@
use rpc_toolkit::reqwest::Client;
use rpc_toolkit::url::{Host, Url};
use rpc_toolkit::Context;
mod cli;
mod rpc;
pub use cli::CliContext;
pub use rpc::RpcContext;
#[derive(Debug, Clone)]
pub struct ExtendedContext<T, U> {
base: T,
extension: U,
}
impl<T, U> ExtendedContext<T, U> {
pub fn map<F: FnOnce(U) -> V, V>(self, f: F) -> ExtendedContext<T, V> {
ExtendedContext {
base: self.base,
extension: f(self.extension),
}
}
pub fn split(self) -> (T, U) {
(self.base, self.extension)
}
pub fn base(&self) -> &T {
&self.base
}
pub fn extension(&self) -> &U {
&self.extension
}
}
impl<T> From<T> for ExtendedContext<T, ()> {
fn from(base: T) -> Self {
ExtendedContext {
base,
extension: (),
}
}
}
impl<T: Context, U> Context for ExtendedContext<T, U> {
fn host(&self) -> Host<&str> {
self.base.host()
}
fn port(&self) -> u16 {
self.base.port()
}
fn protocol(&self) -> &str {
self.base.protocol()
}
fn url(&self) -> Url {
self.base.url()
}
fn client(&self) -> &Client {
self.base.client()
}
}
#[derive(Clone)]
pub enum EitherContext {
Cli(CliContext),
Rpc(RpcContext),
}
impl EitherContext {
pub fn as_cli(&self) -> Option<&CliContext> {
match self {
EitherContext::Cli(a) => Some(a),
_ => None,
}
}
pub fn as_rpc(&self) -> Option<&RpcContext> {
match self {
EitherContext::Rpc(a) => Some(a),
_ => None,
}
}
}
impl Context for EitherContext {
fn host(&self) -> Host<&str> {
match self {
EitherContext::Cli(a) => a.host(),
EitherContext::Rpc(b) => b.host(),
}
}
fn port(&self) -> u16 {
match self {
EitherContext::Cli(a) => a.port(),
EitherContext::Rpc(b) => b.port(),
}
}
fn protocol(&self) -> &str {
match self {
EitherContext::Cli(a) => a.protocol(),
EitherContext::Rpc(b) => b.protocol(),
}
}
fn url(&self) -> Url {
match self {
EitherContext::Cli(a) => a.url(),
EitherContext::Rpc(b) => b.url(),
}
}
fn client(&self) -> &Client {
match self {
EitherContext::Cli(a) => a.client(),
EitherContext::Rpc(b) => b.client(),
}
}
}

79
appmgr/src/context/rpc.rs Normal file
View File

@@ -0,0 +1,79 @@
use std::net::{IpAddr, SocketAddr};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use bollard::Docker;
use patch_db::PatchDb;
use rpc_toolkit::url::Host;
use rpc_toolkit::Context;
use serde::Deserialize;
use sqlx::SqlitePool;
use tokio::fs::File;
use crate::util::{from_yaml_async_reader, AsyncFileExt};
use crate::{Error, ResultExt};
#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct RpcContextConfig {
pub bind: Option<SocketAddr>,
pub db: Option<PathBuf>,
pub secret_store: Option<PathBuf>,
}
pub struct RpcContextSeed {
pub bind: SocketAddr,
pub db: PatchDb,
pub secret_store: SqlitePool,
pub docker: Docker,
}
#[derive(Clone)]
pub struct RpcContext(Arc<RpcContextSeed>);
impl RpcContext {
pub async fn init() -> Result<Self, Error> {
let cfg_path = Path::new(crate::CONFIG_PATH);
let base = if let Some(f) = File::maybe_open(cfg_path)
.await
.with_ctx(|_| (crate::ErrorKind::Filesystem, cfg_path.display().to_string()))?
{
from_yaml_async_reader(f).await?
} else {
RpcContextConfig::default()
};
let seed = Arc::new(RpcContextSeed {
bind: base.bind.unwrap_or(([127, 0, 0, 1], 5959).into()),
db: PatchDb::open(
base.db
.unwrap_or_else(|| Path::new("/mnt/embassy-os/embassy.db").to_owned()),
)
.await?,
secret_store: SqlitePool::connect(&format!(
"sqlite://{}",
base.secret_store
.unwrap_or_else(|| Path::new("/mnt/embassy-os/secrets.db").to_owned())
.display()
))
.await?,
docker: Docker::connect_with_unix_defaults()?,
});
Ok(Self(seed))
}
}
impl Context for RpcContext {
fn host(&self) -> Host<&str> {
match self.0.bind.ip() {
IpAddr::V4(a) => Host::Ipv4(a),
IpAddr::V6(a) => Host::Ipv6(a),
}
}
fn port(&self) -> u16 {
self.0.bind.port()
}
}
impl Deref for RpcContext {
type Target = RpcContextSeed;
fn deref(&self) -> &Self::Target {
&*self.0
}
}