mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 22:39:46 +00:00
feat: Get the health checks for the js
This commit is contained in:
@@ -268,6 +268,7 @@ export class HostSystemStartOs implements Effects {
|
|||||||
>
|
>
|
||||||
}
|
}
|
||||||
setHealth(...[options]: Parameters<T.Effects["setHealth"]>) {
|
setHealth(...[options]: Parameters<T.Effects["setHealth"]>) {
|
||||||
|
console.error("BLUJ sethealth", options)
|
||||||
return this.rpcRound("setHealth", options) as ReturnType<
|
return this.rpcRound("setHealth", options) as ReturnType<
|
||||||
T.Effects["setHealth"]
|
T.Effects["setHealth"]
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -106,46 +106,106 @@ export class MainLoop {
|
|||||||
const { manifest } = this.system
|
const { manifest } = this.system
|
||||||
const effects = this.effects
|
const effects = this.effects
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
return Object.values(manifest["health-checks"]).map((value) => {
|
return Object.entries(manifest["health-checks"]).map(
|
||||||
const name = value.name
|
([healthId, value]) => {
|
||||||
const interval = setInterval(async () => {
|
const interval = setInterval(async () => {
|
||||||
const actionProcedure = value
|
const actionProcedure = value
|
||||||
const timeChanged = Date.now() - start
|
const timeChanged = Date.now() - start
|
||||||
if (actionProcedure.type === "docker") {
|
if (actionProcedure.type === "docker") {
|
||||||
const container = await DockerProcedureContainer.of(
|
const container = await DockerProcedureContainer.of(
|
||||||
effects,
|
effects,
|
||||||
actionProcedure,
|
actionProcedure,
|
||||||
manifest.volumes,
|
manifest.volumes,
|
||||||
)
|
|
||||||
const executed = await container.exec([
|
|
||||||
actionProcedure.entrypoint,
|
|
||||||
...actionProcedure.args,
|
|
||||||
JSON.stringify(timeChanged),
|
|
||||||
])
|
|
||||||
const stderr = executed.stderr.toString()
|
|
||||||
if (stderr)
|
|
||||||
console.error(`Error running health check ${value.name}: ${stderr}`)
|
|
||||||
return executed.stdout.toString()
|
|
||||||
} else {
|
|
||||||
const moduleCode = await this.system.moduleCode
|
|
||||||
const method = moduleCode.health?.[value.name]
|
|
||||||
if (!method)
|
|
||||||
return console.error(
|
|
||||||
`Expecting that thejs health check ${value.name} exists`,
|
|
||||||
)
|
)
|
||||||
return (await method(
|
const executed = await container.exec([
|
||||||
new PolyfillEffects(effects, this.system.manifest),
|
actionProcedure.entrypoint,
|
||||||
timeChanged,
|
...actionProcedure.args,
|
||||||
).then((x) => {
|
JSON.stringify(timeChanged),
|
||||||
if ("result" in x) return x.result
|
])
|
||||||
if ("error" in x)
|
const stderr = executed.stderr.toString()
|
||||||
return console.error("Error getting config: " + x.error)
|
if (stderr)
|
||||||
return console.error("Error getting config: " + x["error-code"][1])
|
console.error(
|
||||||
})) as any
|
`Error running health check ${value.name}: ${stderr}`,
|
||||||
}
|
)
|
||||||
}, EMBASSY_HEALTH_INTERVAL)
|
return executed.stdout.toString()
|
||||||
|
} else {
|
||||||
|
actionProcedure
|
||||||
|
const moduleCode = await this.system.moduleCode
|
||||||
|
const method = moduleCode.health?.[healthId]
|
||||||
|
if (!method) {
|
||||||
|
await effects.setHealth({
|
||||||
|
name: healthId,
|
||||||
|
status: "failure",
|
||||||
|
message: `Expecting that thejs health check ${healthId} exists`,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return { name, interval }
|
const result = await method(
|
||||||
})
|
new PolyfillEffects(effects, this.system.manifest),
|
||||||
|
timeChanged,
|
||||||
|
)
|
||||||
|
|
||||||
|
if ("result" in result) {
|
||||||
|
await effects.setHealth({
|
||||||
|
name: healthId,
|
||||||
|
status: "passing",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ("error" in result) {
|
||||||
|
await effects.setHealth({
|
||||||
|
name: healthId,
|
||||||
|
status: "failure",
|
||||||
|
message: result.error,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!("error-code" in result)) {
|
||||||
|
await effects.setHealth({
|
||||||
|
name: healthId,
|
||||||
|
status: "failure",
|
||||||
|
message: `Unknown error type ${JSON.stringify(result)}`,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const [code, message] = result["error-code"]
|
||||||
|
if (code === 59) {
|
||||||
|
await effects.setHealth({
|
||||||
|
name: healthId,
|
||||||
|
status: "disabled",
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (code === 60) {
|
||||||
|
await effects.setHealth({
|
||||||
|
name: healthId,
|
||||||
|
status: "starting",
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (code === 61) {
|
||||||
|
await effects.setHealth({
|
||||||
|
name: healthId,
|
||||||
|
status: "warning",
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await effects.setHealth({
|
||||||
|
name: healthId,
|
||||||
|
status: "failure",
|
||||||
|
message: `${result["error-code"][0]}: ${result["error-code"][1]}`,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}, EMBASSY_HEALTH_INTERVAL)
|
||||||
|
|
||||||
|
return { name: healthId, interval }
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ use patch_db::json_ptr::JsonPointer;
|
|||||||
use rpc_toolkit::{from_fn, from_fn_async, AnyContext, Context, Empty, HandlerExt, ParentHandler};
|
use rpc_toolkit::{from_fn, from_fn_async, AnyContext, Context, Empty, HandlerExt, ParentHandler};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::disk::mount::filesystem::idmapped::IdMapped;
|
|
||||||
use crate::disk::mount::filesystem::loop_dev::LoopDev;
|
use crate::disk::mount::filesystem::loop_dev::LoopDev;
|
||||||
use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
|
use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -26,6 +25,7 @@ use crate::status::MainStatus;
|
|||||||
use crate::util::clap::FromStrParser;
|
use crate::util::clap::FromStrParser;
|
||||||
use crate::util::{new_guid, Invoke};
|
use crate::util::{new_guid, Invoke};
|
||||||
use crate::{db::model::ExposedUI, service::RunningStatus};
|
use crate::{db::model::ExposedUI, service::RunningStatus};
|
||||||
|
use crate::{disk::mount::filesystem::idmapped::IdMapped, status::health_check::HealthCheckString};
|
||||||
use crate::{echo, ARCH};
|
use crate::{echo, ARCH};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -605,30 +605,22 @@ async fn set_main_status(context: EffectContext, params: SetMainStatus) -> Resul
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct SetHealth {
|
struct SetHealth {
|
||||||
name: HealthCheckId,
|
name: HealthCheckId,
|
||||||
health_result: Option<HealthCheckResult>,
|
status: HealthCheckString,
|
||||||
|
message: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_health(context: EffectContext, params: SetHealth) -> Result<Value, Error> {
|
async fn set_health(
|
||||||
|
context: EffectContext,
|
||||||
|
SetHealth {
|
||||||
|
name,
|
||||||
|
status,
|
||||||
|
message,
|
||||||
|
}: SetHealth,
|
||||||
|
) -> Result<Value, Error> {
|
||||||
|
dbg!(&name);
|
||||||
|
dbg!(&status);
|
||||||
|
dbg!(&message);
|
||||||
let context = context.deref()?;
|
let context = context.deref()?;
|
||||||
// TODO DrBonez + BLU-J Need to change the type from
|
|
||||||
// ```rs
|
|
||||||
// #[serde(tag = "result")]
|
|
||||||
// pub enum HealthCheckResult {
|
|
||||||
// Success,
|
|
||||||
// Disabled,
|
|
||||||
// Starting,
|
|
||||||
// Loading { message: String },
|
|
||||||
// Failure { error: String },
|
|
||||||
// }
|
|
||||||
// ```
|
|
||||||
// to
|
|
||||||
// ```ts
|
|
||||||
// setHealth(o: {
|
|
||||||
// name: string
|
|
||||||
// status: HealthStatus
|
|
||||||
// message?: string
|
|
||||||
// }): Promise<void>
|
|
||||||
// ```
|
|
||||||
|
|
||||||
let package_id = &context.id;
|
let package_id = &context.id;
|
||||||
context
|
context
|
||||||
@@ -648,14 +640,22 @@ async fn set_health(context: EffectContext, params: SetHealth) -> Result<Value,
|
|||||||
match &mut main {
|
match &mut main {
|
||||||
&mut MainStatus::Running { ref mut health, .. }
|
&mut MainStatus::Running { ref mut health, .. }
|
||||||
| &mut MainStatus::BackingUp { ref mut health, .. } => {
|
| &mut MainStatus::BackingUp { ref mut health, .. } => {
|
||||||
health.remove(¶ms.name);
|
health.remove(&name);
|
||||||
if let SetHealth {
|
|
||||||
|
health.insert(
|
||||||
name,
|
name,
|
||||||
health_result: Some(health_result),
|
match status {
|
||||||
} = params
|
HealthCheckString::Disabled => HealthCheckResult::Disabled,
|
||||||
{
|
HealthCheckString::Passing => HealthCheckResult::Success,
|
||||||
health.insert(name, health_result);
|
HealthCheckString::Starting => HealthCheckResult::Starting,
|
||||||
}
|
HealthCheckString::Warning => HealthCheckResult::Loading {
|
||||||
|
message: message.unwrap_or_default(),
|
||||||
|
},
|
||||||
|
HealthCheckString::Failure => HealthCheckResult::Failure {
|
||||||
|
error: message.unwrap_or_default(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_ => return Ok(()),
|
_ => return Ok(()),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,3 +22,13 @@ impl std::fmt::Display for HealthCheckResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub enum HealthCheckString {
|
||||||
|
Passing,
|
||||||
|
Disabled,
|
||||||
|
Starting,
|
||||||
|
Warning,
|
||||||
|
Failure,
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,10 +47,10 @@ export function healthCheck(o: {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
await o.effects.setHealth({
|
await o.effects.setHealth({
|
||||||
name: o.name,
|
name: o.name,
|
||||||
status: "failing",
|
status: "failure",
|
||||||
message: asMessage(e),
|
message: asMessage(e),
|
||||||
})
|
})
|
||||||
currentValue.lastResult = "failing"
|
currentValue.lastResult = "failure"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export async function checkPortListening(
|
|||||||
return { status: "passing", message: options.successMessage }
|
return { status: "passing", message: options.successMessage }
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
status: "failing",
|
status: "failure",
|
||||||
message: options.errorMessage,
|
message: options.errorMessage,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -56,7 +56,7 @@ export async function checkPortListening(
|
|||||||
setTimeout(
|
setTimeout(
|
||||||
() =>
|
() =>
|
||||||
resolve({
|
resolve({
|
||||||
status: "failing",
|
status: "failure",
|
||||||
message:
|
message:
|
||||||
options.timeoutMessage || `Timeout trying to check port ${port}`,
|
options.timeoutMessage || `Timeout trying to check port ${port}`,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -19,14 +19,17 @@ export const checkWebUrl = async (
|
|||||||
} = {},
|
} = {},
|
||||||
): Promise<CheckResult> => {
|
): Promise<CheckResult> => {
|
||||||
return Promise.race([fetch(url), timeoutPromise(timeout)])
|
return Promise.race([fetch(url), timeoutPromise(timeout)])
|
||||||
.then((x) => ({
|
.then(
|
||||||
status: "passing" as const,
|
(x) =>
|
||||||
message: successMessage,
|
({
|
||||||
}))
|
status: "passing",
|
||||||
|
message: successMessage,
|
||||||
|
}) as const,
|
||||||
|
)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.warn(`Error while fetching URL: ${url}`)
|
console.warn(`Error while fetching URL: ${url}`)
|
||||||
console.error(JSON.stringify(e))
|
console.error(JSON.stringify(e))
|
||||||
console.error(e.toString())
|
console.error(e.toString())
|
||||||
return { status: "failing" as const, message: errorMessage }
|
return { status: "failure" as const, message: errorMessage }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const runHealthScript = async (
|
|||||||
console.warn(errorMessage)
|
console.warn(errorMessage)
|
||||||
console.warn(JSON.stringify(e))
|
console.warn(JSON.stringify(e))
|
||||||
console.warn(e.toString())
|
console.warn(e.toString())
|
||||||
throw { status: "failing", message: errorMessage } as CheckResult
|
throw { status: "failure", message: errorMessage } as CheckResult
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
status: "passing",
|
status: "passing",
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export class Daemons<Manifest extends SDKManifest, Ids extends string> {
|
|||||||
const response = await Promise.resolve(daemon.ready.fn()).catch(
|
const response = await Promise.resolve(daemon.ready.fn()).catch(
|
||||||
(err) =>
|
(err) =>
|
||||||
({
|
({
|
||||||
status: "failing",
|
status: "failure",
|
||||||
message: "message" in err ? err.message : String(err),
|
message: "message" in err ? err.message : String(err),
|
||||||
}) as CheckResult,
|
}) as CheckResult,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -133,7 +133,12 @@ export type Daemon = {
|
|||||||
[DaemonProof]: never
|
[DaemonProof]: never
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HealthStatus = "passing" | "warning" | "failing" | "disabled"
|
export type HealthStatus =
|
||||||
|
| `passing`
|
||||||
|
| `disabled`
|
||||||
|
| `starting`
|
||||||
|
| `warning`
|
||||||
|
| `failure`
|
||||||
|
|
||||||
export type SmtpValue = {
|
export type SmtpValue = {
|
||||||
server: string
|
server: string
|
||||||
|
|||||||
Reference in New Issue
Block a user