mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
Feature/sdk040dependencies (#2609)
* update registry upload to take id for new admin permissions (#2605) * wip * wip: Get the get dependencies * wip check_dependencies * wip: Get the build working to the vm * wip: Add in the last of the things that where needed for the new sdk * Add fix * wip: implement the changes * wip: Fix the naming --------- Co-authored-by: Lucy <12953208+elvece@users.noreply.github.com>
This commit is contained in:
@@ -74,12 +74,14 @@ async fn do_upload(
|
||||
mut url: Url,
|
||||
user: &str,
|
||||
pass: &str,
|
||||
pkg_id: &str,
|
||||
body: Body,
|
||||
) -> Result<(), Error> {
|
||||
url.set_path("/admin/v0/upload");
|
||||
let req = httpc
|
||||
.post(url)
|
||||
.header(header::ACCEPT, "text/plain")
|
||||
.query(&["id", pkg_id])
|
||||
.basic_auth(user, Some(pass))
|
||||
.body(body)
|
||||
.build()?;
|
||||
@@ -198,6 +200,7 @@ pub async fn publish(
|
||||
registry.clone(),
|
||||
&user,
|
||||
&pass,
|
||||
&pkg.id,
|
||||
Body::wrap_stream(file_stream),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -11,6 +11,7 @@ use clap::Parser;
|
||||
use emver::VersionRange;
|
||||
use imbl::OrdMap;
|
||||
use imbl_value::{json, InternedString};
|
||||
use itertools::Itertools;
|
||||
use models::{
|
||||
ActionId, DataUrl, HealthCheckId, HostId, Id, ImageId, PackageId, ServiceInterfaceId, VolumeId,
|
||||
};
|
||||
@@ -23,6 +24,7 @@ use url::Url;
|
||||
|
||||
use crate::db::model::package::{
|
||||
ActionMetadata, CurrentDependencies, CurrentDependencyInfo, CurrentDependencyKind,
|
||||
ManifestPreference,
|
||||
};
|
||||
use crate::disk::mount::filesystem::idmapped::IdMapped;
|
||||
use crate::disk::mount::filesystem::loop_dev::LoopDev;
|
||||
@@ -154,6 +156,18 @@ pub fn service_effect_handler() -> ParentHandler {
|
||||
.no_display()
|
||||
.with_remote_cli::<ContainerCliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"getDependencies",
|
||||
from_fn_async(get_dependencies)
|
||||
.no_display()
|
||||
.with_remote_cli::<ContainerCliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"checkDependencies",
|
||||
from_fn_async(check_dependencies)
|
||||
.no_display()
|
||||
.with_remote_cli::<ContainerCliContext>(),
|
||||
)
|
||||
.subcommand("getSystemSmtp", from_fn_async(get_system_smtp).no_cli())
|
||||
.subcommand("getContainerIp", from_fn_async(get_container_ip).no_cli())
|
||||
.subcommand(
|
||||
@@ -178,6 +192,7 @@ pub fn service_effect_handler() -> ParentHandler {
|
||||
.subcommand("removeAction", from_fn_async(remove_action).no_cli())
|
||||
.subcommand("reverseProxy", from_fn_async(reverse_proxy).no_cli())
|
||||
.subcommand("mount", from_fn_async(mount).no_cli())
|
||||
|
||||
// TODO Callbacks
|
||||
}
|
||||
|
||||
@@ -1270,3 +1285,138 @@ async fn set_dependencies(
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_dependencies(ctx: EffectContext) -> Result<Vec<DependencyRequirement>, Error> {
|
||||
let ctx = ctx.deref()?;
|
||||
let id = &ctx.id;
|
||||
let db = ctx.ctx.db.peek().await;
|
||||
let data = db
|
||||
.as_public()
|
||||
.as_package_data()
|
||||
.as_idx(id)
|
||||
.or_not_found(id)?
|
||||
.as_current_dependencies()
|
||||
.de()?;
|
||||
|
||||
data.0
|
||||
.into_iter()
|
||||
.map(|(id, current_dependency_info)| {
|
||||
let CurrentDependencyInfo {
|
||||
registry_url,
|
||||
version_spec,
|
||||
kind,
|
||||
..
|
||||
} = current_dependency_info;
|
||||
Ok::<_, Error>(match kind {
|
||||
CurrentDependencyKind::Exists => DependencyRequirement::Exists {
|
||||
id,
|
||||
registry_url,
|
||||
version_spec,
|
||||
},
|
||||
CurrentDependencyKind::Running { health_checks } => {
|
||||
DependencyRequirement::Running {
|
||||
id,
|
||||
health_checks,
|
||||
version_spec,
|
||||
registry_url,
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.try_collect()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
struct CheckDependenciesParam {
|
||||
package_ids: Option<Vec<PackageId>>,
|
||||
}
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
struct CheckDependenciesResult {
|
||||
package_id: PackageId,
|
||||
is_installed: bool,
|
||||
is_running: bool,
|
||||
health_checks: Vec<HealthCheckResult>,
|
||||
#[ts(type = "string | null")]
|
||||
version: Option<emver::Version>,
|
||||
}
|
||||
|
||||
async fn check_dependencies(
|
||||
ctx: EffectContext,
|
||||
CheckDependenciesParam { package_ids }: CheckDependenciesParam,
|
||||
) -> Result<Vec<CheckDependenciesResult>, Error> {
|
||||
let ctx = ctx.deref()?;
|
||||
let db = ctx.ctx.db.peek().await;
|
||||
let current_dependencies = db
|
||||
.as_public()
|
||||
.as_package_data()
|
||||
.as_idx(&ctx.id)
|
||||
.or_not_found(&ctx.id)?
|
||||
.as_current_dependencies()
|
||||
.de()?;
|
||||
let package_ids: Vec<_> = package_ids
|
||||
.unwrap_or_else(|| current_dependencies.0.keys().cloned().collect())
|
||||
.into_iter()
|
||||
.filter_map(|x| {
|
||||
let info = current_dependencies.0.get(&x)?;
|
||||
Some((x, info))
|
||||
})
|
||||
.collect();
|
||||
let mut results = Vec::with_capacity(package_ids.len());
|
||||
|
||||
for (package_id, dependency_info) in package_ids {
|
||||
let Some(package) = db.as_public().as_package_data().as_idx(&package_id) else {
|
||||
results.push(CheckDependenciesResult {
|
||||
package_id,
|
||||
is_installed: false,
|
||||
is_running: false,
|
||||
health_checks: vec![],
|
||||
version: None,
|
||||
});
|
||||
continue;
|
||||
};
|
||||
let installed_version = package
|
||||
.as_state_info()
|
||||
.as_manifest(ManifestPreference::New)
|
||||
.as_version()
|
||||
.de()?
|
||||
.into_version();
|
||||
let version = Some(installed_version.clone());
|
||||
if !installed_version.satisfies(&dependency_info.version_spec) {
|
||||
results.push(CheckDependenciesResult {
|
||||
package_id,
|
||||
is_installed: false,
|
||||
is_running: false,
|
||||
health_checks: vec![],
|
||||
version,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
let is_installed = true;
|
||||
let status = package.as_status().as_main().de()?;
|
||||
let is_running = if is_installed {
|
||||
status.running()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let health_checks = status
|
||||
.health()
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|(_, val)| val)
|
||||
.collect();
|
||||
results.push(CheckDependenciesResult {
|
||||
package_id,
|
||||
is_installed,
|
||||
is_running,
|
||||
health_checks,
|
||||
version,
|
||||
});
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::builder::ValueParserFactory;
|
||||
pub use models::HealthCheckId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::util::clap::FromStrParser;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct HealthCheckResult {
|
||||
@@ -9,6 +14,45 @@ pub struct HealthCheckResult {
|
||||
#[serde(flatten)]
|
||||
pub kind: HealthCheckResultKind,
|
||||
}
|
||||
// healthCheckName:kind:message OR healthCheckName:kind
|
||||
impl FromStr for HealthCheckResult {
|
||||
type Err = color_eyre::eyre::Report;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let from_parts = |name: &str, kind: &str, message: Option<&str>| {
|
||||
let message = message.map(|x| x.to_string());
|
||||
let kind = match kind {
|
||||
"success" => HealthCheckResultKind::Success { message },
|
||||
"disabled" => HealthCheckResultKind::Disabled { message },
|
||||
"starting" => HealthCheckResultKind::Starting { message },
|
||||
"loading" => HealthCheckResultKind::Loading {
|
||||
message: message.unwrap_or_default(),
|
||||
},
|
||||
"failure" => HealthCheckResultKind::Failure {
|
||||
message: message.unwrap_or_default(),
|
||||
},
|
||||
_ => return Err(color_eyre::eyre::eyre!("Invalid health check kind")),
|
||||
};
|
||||
Ok(Self {
|
||||
name: name.to_string(),
|
||||
kind,
|
||||
})
|
||||
};
|
||||
let parts = s.split(':').collect::<Vec<_>>();
|
||||
match &*parts {
|
||||
[name, kind, message] => from_parts(name, kind, Some(message)),
|
||||
[name, kind] => from_parts(name, kind, None),
|
||||
_ => Err(color_eyre::eyre::eyre!(
|
||||
"Could not match the shape of the result ${parts:?}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ValueParserFactory for HealthCheckResult {
|
||||
type Parser = FromStrParser<Self>;
|
||||
fn value_parser() -> Self::Parser {
|
||||
FromStrParser::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
@@ -91,4 +91,13 @@ impl MainStatus {
|
||||
};
|
||||
MainStatus::BackingUp { started, health }
|
||||
}
|
||||
|
||||
pub fn health(&self) -> Option<&OrdMap<HealthCheckId, HealthCheckResult>> {
|
||||
match self {
|
||||
MainStatus::Running { health, .. } => Some(health),
|
||||
MainStatus::BackingUp { health, .. } => Some(health),
|
||||
MainStatus::Stopped | MainStatus::Stopping { .. } | MainStatus::Restarting => None,
|
||||
MainStatus::Starting { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user