mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
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:
committed by
Aiden McClelland
parent
5741cf084f
commit
8954e3e338
135
appmgr/src/context/cli.rs
Normal file
135
appmgr/src/context/cli.rs
Normal 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
109
appmgr/src/context/mod.rs
Normal 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
79
appmgr/src/context/rpc.rs
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user