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:
Jade
2024-04-26 17:51:33 -06:00
committed by GitHub
parent e08d93b2aa
commit 8a38666105
24 changed files with 417 additions and 39 deletions

View File

@@ -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?;

View File

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

View File

@@ -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")]

View File

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