From 058bfe0737dd0055e18a27d63aba36fc16611219 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Thu, 8 Aug 2024 11:10:02 -0600 Subject: [PATCH] sdk updates --- .../Systems/SystemForEmbassy/index.ts | 1 - core/startos/src/net/service_interface.rs | 1 - .../startos/src/service/effects/dependency.rs | 4 +- core/startos/src/service/effects/health.rs | 4 +- .../src/service/effects/net/interface.rs | 3 - core/startos/src/service/mod.rs | 4 +- core/startos/src/status/health_check.rs | 36 ++++---- core/startos/src/status/mod.rs | 18 ++-- sdk/lib/StartSdk.ts | 4 +- sdk/lib/health/HealthCheck.ts | 92 ++++++++----------- sdk/lib/health/checkFns/CheckResult.ts | 6 -- sdk/lib/health/checkFns/HealthCheckResult.ts | 3 + sdk/lib/health/checkFns/checkPortListening.ts | 12 +-- sdk/lib/health/checkFns/checkWebUrl.ts | 8 +- sdk/lib/health/checkFns/index.ts | 2 +- sdk/lib/health/checkFns/runHealthScript.ts | 11 +-- sdk/lib/interfaces/Origin.ts | 2 - sdk/lib/interfaces/ServiceInterfaceBuilder.ts | 1 - sdk/lib/mainFn/Daemons.ts | 4 +- sdk/lib/mainFn/HealthDaemon.ts | 51 ++++------ sdk/lib/osBindings/CheckDependenciesResult.ts | 4 +- .../ExportServiceInterfaceParams.ts | 1 - sdk/lib/osBindings/MainStatus.ts | 6 +- ...eckResult.ts => NamedHealthCheckResult.ts} | 2 +- sdk/lib/osBindings/ServiceInterface.ts | 1 - sdk/lib/osBindings/index.ts | 2 +- sdk/lib/test/host.test.ts | 1 - sdk/lib/trigger/TriggerInput.ts | 1 - sdk/lib/trigger/changeOnFirstSuccess.ts | 8 +- sdk/lib/trigger/defaultTrigger.ts | 6 +- sdk/lib/trigger/lastStatus.ts | 33 +++++++ sdk/lib/trigger/successFailure.ts | 31 +------ sdk/lib/types.ts | 4 +- sdk/lib/util/Overlay.ts | 17 ++++ sdk/lib/util/getServiceInterface.ts | 2 - .../app-show-health-checks.component.ts | 6 +- .../app-show/pipes/health-color.pipe.ts | 2 +- .../app-show/pipes/to-health-checks.pipe.ts | 2 +- .../ui/src/app/services/api/api.fixures.ts | 7 -- .../ui/src/app/services/api/api.types.ts | 2 +- .../ui/src/app/services/api/mock-patch.ts | 6 -- 41 files changed, 190 insertions(+), 221 deletions(-) delete mode 100644 sdk/lib/health/checkFns/CheckResult.ts create mode 100644 sdk/lib/health/checkFns/HealthCheckResult.ts rename sdk/lib/osBindings/{HealthCheckResult.ts => NamedHealthCheckResult.ts} (85%) create mode 100644 sdk/lib/trigger/lastStatus.ts diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts index f639d00cd..8b74a7217 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts @@ -445,7 +445,6 @@ export class SystemForEmbassy implements System { id: `${id}-${internal}`, description: interfaceValue.description, hasPrimary: false, - disabled: false, type: interfaceValue.ui && (origin.scheme === "http" || origin.sslScheme === "https") diff --git a/core/startos/src/net/service_interface.rs b/core/startos/src/net/service_interface.rs index dbe228ef2..b1824140b 100644 --- a/core/startos/src/net/service_interface.rs +++ b/core/startos/src/net/service_interface.rs @@ -67,7 +67,6 @@ pub struct ServiceInterface { pub name: String, pub description: String, pub has_primary: bool, - pub disabled: bool, pub masked: bool, pub address_info: AddressInfo, #[serde(rename = "type")] diff --git a/core/startos/src/service/effects/dependency.rs b/core/startos/src/service/effects/dependency.rs index ad5ec2e9b..fbd02800c 100644 --- a/core/startos/src/service/effects/dependency.rs +++ b/core/startos/src/service/effects/dependency.rs @@ -17,7 +17,7 @@ use crate::disk::mount::filesystem::idmapped::IdMapped; use crate::disk::mount::filesystem::{FileSystem, MountType}; use crate::rpc_continuations::Guid; use crate::service::effects::prelude::*; -use crate::status::health_check::HealthCheckResult; +use crate::status::health_check::NamedHealthCheckResult; use crate::util::clap::FromStrParser; use crate::util::Invoke; use crate::volume::data_dir; @@ -319,7 +319,7 @@ pub struct CheckDependenciesResult { is_installed: bool, is_running: bool, config_satisfied: bool, - health_checks: BTreeMap, + health_checks: BTreeMap, #[ts(type = "string | null")] version: Option, } diff --git a/core/startos/src/service/effects/health.rs b/core/startos/src/service/effects/health.rs index c8ef8fc4e..aad06a004 100644 --- a/core/startos/src/service/effects/health.rs +++ b/core/startos/src/service/effects/health.rs @@ -1,7 +1,7 @@ use models::HealthCheckId; use crate::service::effects::prelude::*; -use crate::status::health_check::HealthCheckResult; +use crate::status::health_check::NamedHealthCheckResult; use crate::status::MainStatus; #[derive(Debug, Clone, Serialize, Deserialize, TS)] @@ -10,7 +10,7 @@ use crate::status::MainStatus; pub struct SetHealth { id: HealthCheckId, #[serde(flatten)] - result: HealthCheckResult, + result: NamedHealthCheckResult, } pub async fn set_health( context: EffectContext, diff --git a/core/startos/src/service/effects/net/interface.rs b/core/startos/src/service/effects/net/interface.rs index e636e9b57..6cd4cd4c9 100644 --- a/core/startos/src/service/effects/net/interface.rs +++ b/core/startos/src/service/effects/net/interface.rs @@ -16,7 +16,6 @@ pub struct ExportServiceInterfaceParams { name: String, description: String, has_primary: bool, - disabled: bool, masked: bool, address_info: AddressInfo, r#type: ServiceInterfaceType, @@ -28,7 +27,6 @@ pub async fn export_service_interface( name, description, has_primary, - disabled, masked, address_info, r#type, @@ -42,7 +40,6 @@ pub async fn export_service_interface( name, description, has_primary, - disabled, masked, address_info, interface_type: r#type, diff --git a/core/startos/src/service/mod.rs b/core/startos/src/service/mod.rs index 2beb5c9fa..6a99841d6 100644 --- a/core/startos/src/service/mod.rs +++ b/core/startos/src/service/mod.rs @@ -27,7 +27,7 @@ use crate::progress::{NamedProgress, Progress}; use crate::rpc_continuations::Guid; use crate::s9pk::S9pk; use crate::service::service_map::InstallProgressHandles; -use crate::status::health_check::HealthCheckResult; +use crate::status::health_check::NamedHealthCheckResult; use crate::util::actor::concurrent::ConcurrentActor; use crate::util::io::create_file; use crate::util::serde::{NoOutput, Pem}; @@ -493,7 +493,7 @@ impl Service { #[derive(Debug, Clone)] pub struct RunningStatus { - health: OrdMap, + health: OrdMap, started: DateTime, } diff --git a/core/startos/src/status/health_check.rs b/core/startos/src/status/health_check.rs index 90b20f8c5..1b1e2a7b6 100644 --- a/core/startos/src/status/health_check.rs +++ b/core/startos/src/status/health_check.rs @@ -9,25 +9,25 @@ use crate::util::clap::FromStrParser; #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, TS)] #[serde(rename_all = "camelCase")] -pub struct HealthCheckResult { +pub struct NamedHealthCheckResult { pub name: String, #[serde(flatten)] - pub kind: HealthCheckResultKind, + pub kind: NamedHealthCheckResultKind, } // healthCheckName:kind:message OR healthCheckName:kind -impl FromStr for HealthCheckResult { +impl FromStr for NamedHealthCheckResult { type Err = color_eyre::eyre::Report; fn from_str(s: &str) -> Result { 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 { + "success" => NamedHealthCheckResultKind::Success { message }, + "disabled" => NamedHealthCheckResultKind::Disabled { message }, + "starting" => NamedHealthCheckResultKind::Starting { message }, + "loading" => NamedHealthCheckResultKind::Loading { message: message.unwrap_or_default(), }, - "failure" => HealthCheckResultKind::Failure { + "failure" => NamedHealthCheckResultKind::Failure { message: message.unwrap_or_default(), }, _ => return Err(color_eyre::eyre::eyre!("Invalid health check kind")), @@ -47,7 +47,7 @@ impl FromStr for HealthCheckResult { } } } -impl ValueParserFactory for HealthCheckResult { +impl ValueParserFactory for NamedHealthCheckResult { type Parser = FromStrParser; fn value_parser() -> Self::Parser { FromStrParser::new() @@ -57,40 +57,44 @@ impl ValueParserFactory for HealthCheckResult { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, TS)] #[serde(rename_all = "camelCase")] #[serde(tag = "result")] -pub enum HealthCheckResultKind { +pub enum NamedHealthCheckResultKind { Success { message: Option }, Disabled { message: Option }, Starting { message: Option }, Loading { message: String }, Failure { message: String }, } -impl std::fmt::Display for HealthCheckResult { +impl std::fmt::Display for NamedHealthCheckResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let name = &self.name; match &self.kind { - HealthCheckResultKind::Success { message } => { + NamedHealthCheckResultKind::Success { message } => { if let Some(message) = message { write!(f, "{name}: Succeeded ({message})") } else { write!(f, "{name}: Succeeded") } } - HealthCheckResultKind::Disabled { message } => { + NamedHealthCheckResultKind::Disabled { message } => { if let Some(message) = message { write!(f, "{name}: Disabled ({message})") } else { write!(f, "{name}: Disabled") } } - HealthCheckResultKind::Starting { message } => { + NamedHealthCheckResultKind::Starting { message } => { if let Some(message) = message { write!(f, "{name}: Starting ({message})") } else { write!(f, "{name}: Starting") } } - HealthCheckResultKind::Loading { message } => write!(f, "{name}: Loading ({message})"), - HealthCheckResultKind::Failure { message } => write!(f, "{name}: Failed ({message})"), + NamedHealthCheckResultKind::Loading { message } => { + write!(f, "{name}: Loading ({message})") + } + NamedHealthCheckResultKind::Failure { message } => { + write!(f, "{name}: Failed ({message})") + } } } } diff --git a/core/startos/src/status/mod.rs b/core/startos/src/status/mod.rs index c1d3a36ad..1701a965e 100644 --- a/core/startos/src/status/mod.rs +++ b/core/startos/src/status/mod.rs @@ -1,4 +1,5 @@ -use std::{collections::BTreeMap, sync::Arc}; +use std::collections::BTreeMap; +use std::sync::Arc; use chrono::{DateTime, Utc}; use imbl::OrdMap; @@ -6,8 +7,9 @@ use serde::{Deserialize, Serialize}; use ts_rs::TS; use self::health_check::HealthCheckId; -use crate::status::health_check::HealthCheckResult; -use crate::{prelude::*, util::GeneralGuard}; +use crate::prelude::*; +use crate::status::health_check::NamedHealthCheckResult; +use crate::util::GeneralGuard; pub mod health_check; #[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS)] @@ -32,15 +34,15 @@ pub enum MainStatus { Running { #[ts(type = "string")] started: DateTime, - #[ts(as = "BTreeMap")] - health: OrdMap, + #[ts(as = "BTreeMap")] + health: OrdMap, }, #[serde(rename_all = "camelCase")] BackingUp { #[ts(type = "string | null")] started: Option>, - #[ts(as = "BTreeMap")] - health: OrdMap, + #[ts(as = "BTreeMap")] + health: OrdMap, }, } impl MainStatus { @@ -93,7 +95,7 @@ impl MainStatus { MainStatus::BackingUp { started, health } } - pub fn health(&self) -> Option<&OrdMap> { + pub fn health(&self) -> Option<&OrdMap> { match self { MainStatus::Running { health, .. } => Some(health), MainStatus::BackingUp { health, .. } => Some(health), diff --git a/sdk/lib/StartSdk.ts b/sdk/lib/StartSdk.ts index 7cdedff90..484817b76 100644 --- a/sdk/lib/StartSdk.ts +++ b/sdk/lib/StartSdk.ts @@ -293,8 +293,8 @@ export class StartSdk { ) }, HealthCheck: { - of(o: HealthCheckParams) { - return healthCheck(o) + of(o: HealthCheckParams) { + return healthCheck(o) }, }, Dependency: { diff --git a/sdk/lib/health/HealthCheck.ts b/sdk/lib/health/HealthCheck.ts index 4b72dbf61..adb00e296 100644 --- a/sdk/lib/health/HealthCheck.ts +++ b/sdk/lib/health/HealthCheck.ts @@ -1,5 +1,5 @@ import { Effects } from "../types" -import { CheckResult } from "./checkFns/CheckResult" +import { HealthCheckResult } from "./checkFns/HealthCheckResult" import { HealthReceipt } from "./HealthReceipt" import { Trigger } from "../trigger" import { TriggerInput } from "../trigger/TriggerInput" @@ -9,66 +9,52 @@ import { Overlay } from "../util/Overlay" import { object, unknown } from "ts-matches" import * as T from "../types" -export type HealthCheckParams = { +export type HealthCheckParams = { effects: Effects name: string - image: { - id: keyof Manifest["images"] & T.ImageId - sharedRun?: boolean - } trigger?: Trigger - fn(overlay: Overlay): Promise | CheckResult + fn(): Promise | HealthCheckResult onFirstSuccess?: () => unknown | Promise } -export function healthCheck( - o: HealthCheckParams, -) { +export function healthCheck(o: HealthCheckParams) { new Promise(async () => { - const overlay = await Overlay.of(o.effects, o.image) - try { - let currentValue: TriggerInput = { - hadSuccess: false, + let currentValue: TriggerInput = {} + const getCurrentValue = () => currentValue + const trigger = (o.trigger ?? defaultTrigger)(getCurrentValue) + const triggerFirstSuccess = once(() => + Promise.resolve( + "onFirstSuccess" in o && o.onFirstSuccess + ? o.onFirstSuccess() + : undefined, + ), + ) + for ( + let res = await trigger.next(); + !res.done; + res = await trigger.next() + ) { + try { + const { result, message } = await o.fn() + await o.effects.setHealth({ + name: o.name, + id: o.name, + result, + message: message || "", + }) + currentValue.lastResult = result + await triggerFirstSuccess().catch((err) => { + console.error(err) + }) + } catch (e) { + await o.effects.setHealth({ + name: o.name, + id: o.name, + result: "failure", + message: asMessage(e) || "", + }) + currentValue.lastResult = "failure" } - const getCurrentValue = () => currentValue - const trigger = (o.trigger ?? defaultTrigger)(getCurrentValue) - const triggerFirstSuccess = once(() => - Promise.resolve( - "onFirstSuccess" in o && o.onFirstSuccess - ? o.onFirstSuccess() - : undefined, - ), - ) - for ( - let res = await trigger.next(); - !res.done; - res = await trigger.next() - ) { - try { - const { status, message } = await o.fn(overlay) - await o.effects.setHealth({ - name: o.name, - id: o.name, - result: status, - message: message || "", - }) - currentValue.hadSuccess = true - currentValue.lastResult = "success" - await triggerFirstSuccess().catch((err) => { - console.error(err) - }) - } catch (e) { - await o.effects.setHealth({ - name: o.name, - id: o.name, - result: "failure", - message: asMessage(e) || "", - }) - currentValue.lastResult = "failure" - } - } - } finally { - await overlay.destroy() } }) return {} as HealthReceipt diff --git a/sdk/lib/health/checkFns/CheckResult.ts b/sdk/lib/health/checkFns/CheckResult.ts deleted file mode 100644 index 8b46ee5c4..000000000 --- a/sdk/lib/health/checkFns/CheckResult.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { HealthStatus } from "../../types" - -export type CheckResult = { - status: HealthStatus - message: string | null -} diff --git a/sdk/lib/health/checkFns/HealthCheckResult.ts b/sdk/lib/health/checkFns/HealthCheckResult.ts new file mode 100644 index 000000000..ba2468488 --- /dev/null +++ b/sdk/lib/health/checkFns/HealthCheckResult.ts @@ -0,0 +1,3 @@ +import { T } from "../.." + +export type HealthCheckResult = Omit diff --git a/sdk/lib/health/checkFns/checkPortListening.ts b/sdk/lib/health/checkFns/checkPortListening.ts index 4cc0738da..94d0becc0 100644 --- a/sdk/lib/health/checkFns/checkPortListening.ts +++ b/sdk/lib/health/checkFns/checkPortListening.ts @@ -1,6 +1,6 @@ import { Effects } from "../../types" import { stringFromStdErrOut } from "../../util/stringFromStdErrOut" -import { CheckResult } from "./CheckResult" +import { HealthCheckResult } from "./HealthCheckResult" import { promisify } from "node:util" import * as CP from "node:child_process" @@ -32,8 +32,8 @@ export async function checkPortListening( timeoutMessage?: string timeout?: number }, -): Promise { - return Promise.race([ +): Promise { + return Promise.race([ Promise.resolve().then(async () => { const hasAddress = containsAddress( @@ -45,10 +45,10 @@ export async function checkPortListening( port, ) if (hasAddress) { - return { status: "success", message: options.successMessage } + return { result: "success", message: options.successMessage } } return { - status: "failure", + result: "failure", message: options.errorMessage, } }), @@ -56,7 +56,7 @@ export async function checkPortListening( setTimeout( () => resolve({ - status: "failure", + result: "failure", message: options.timeoutMessage || `Timeout trying to check port ${port}`, }), diff --git a/sdk/lib/health/checkFns/checkWebUrl.ts b/sdk/lib/health/checkFns/checkWebUrl.ts index 8f61ae2ef..b25c792e1 100644 --- a/sdk/lib/health/checkFns/checkWebUrl.ts +++ b/sdk/lib/health/checkFns/checkWebUrl.ts @@ -1,5 +1,5 @@ import { Effects } from "../../types" -import { CheckResult } from "./CheckResult" +import { HealthCheckResult } from "./HealthCheckResult" import { timeoutPromise } from "./index" import "isomorphic-fetch" @@ -17,12 +17,12 @@ export const checkWebUrl = async ( successMessage = `Reached ${url}`, errorMessage = `Error while fetching URL: ${url}`, } = {}, -): Promise => { +): Promise => { return Promise.race([fetch(url), timeoutPromise(timeout)]) .then( (x) => ({ - status: "success", + result: "success", message: successMessage, }) as const, ) @@ -30,6 +30,6 @@ export const checkWebUrl = async ( console.warn(`Error while fetching URL: ${url}`) console.error(JSON.stringify(e)) console.error(e.toString()) - return { status: "failure" as const, message: errorMessage } + return { result: "failure" as const, message: errorMessage } }) } diff --git a/sdk/lib/health/checkFns/index.ts b/sdk/lib/health/checkFns/index.ts index d33d5ad0d..2de37e38c 100644 --- a/sdk/lib/health/checkFns/index.ts +++ b/sdk/lib/health/checkFns/index.ts @@ -1,6 +1,6 @@ import { runHealthScript } from "./runHealthScript" export { checkPortListening } from "./checkPortListening" -export { CheckResult } from "./CheckResult" +export { HealthCheckResult } from "./HealthCheckResult" export { checkWebUrl } from "./checkWebUrl" export function timeoutPromise(ms: number, { message = "Timed out" } = {}) { diff --git a/sdk/lib/health/checkFns/runHealthScript.ts b/sdk/lib/health/checkFns/runHealthScript.ts index f0f41ee91..87fc6c69c 100644 --- a/sdk/lib/health/checkFns/runHealthScript.ts +++ b/sdk/lib/health/checkFns/runHealthScript.ts @@ -1,7 +1,7 @@ import { Effects } from "../../types" import { Overlay } from "../../util/Overlay" import { stringFromStdErrOut } from "../../util/stringFromStdErrOut" -import { CheckResult } from "./CheckResult" +import { HealthCheckResult } from "./HealthCheckResult" import { timeoutPromise } from "./index" /** @@ -12,7 +12,6 @@ import { timeoutPromise } from "./index" * @returns */ export const runHealthScript = async ( - effects: Effects, runCommand: string[], overlay: Overlay, { @@ -21,7 +20,7 @@ export const runHealthScript = async ( message = (res: string) => `Have ran script ${runCommand} and the result: ${res}`, } = {}, -): Promise => { +): Promise => { const res = await Promise.race([ overlay.exec(runCommand), timeoutPromise(timeout), @@ -29,10 +28,10 @@ export const runHealthScript = async ( console.warn(errorMessage) console.warn(JSON.stringify(e)) console.warn(e.toString()) - throw { status: "failure", message: errorMessage } as CheckResult + throw { result: "failure", message: errorMessage } as HealthCheckResult }) return { - status: "success", + result: "success", message: message(res.stdout.toString()), - } as CheckResult + } as HealthCheckResult } diff --git a/sdk/lib/interfaces/Origin.ts b/sdk/lib/interfaces/Origin.ts index 52afe1ed3..cc84728ec 100644 --- a/sdk/lib/interfaces/Origin.ts +++ b/sdk/lib/interfaces/Origin.ts @@ -47,7 +47,6 @@ export class Origin { name, description, hasPrimary, - disabled, id, type, username, @@ -69,7 +68,6 @@ export class Origin { name, description, hasPrimary, - disabled, addressInfo, type, masked, diff --git a/sdk/lib/interfaces/ServiceInterfaceBuilder.ts b/sdk/lib/interfaces/ServiceInterfaceBuilder.ts index 14eaee1d3..49d8020d6 100644 --- a/sdk/lib/interfaces/ServiceInterfaceBuilder.ts +++ b/sdk/lib/interfaces/ServiceInterfaceBuilder.ts @@ -21,7 +21,6 @@ export class ServiceInterfaceBuilder { id: string description: string hasPrimary: boolean - disabled: boolean type: ServiceInterfaceType username: string | null path: string diff --git a/sdk/lib/mainFn/Daemons.ts b/sdk/lib/mainFn/Daemons.ts index c766e2f2e..3134d0459 100644 --- a/sdk/lib/mainFn/Daemons.ts +++ b/sdk/lib/mainFn/Daemons.ts @@ -1,6 +1,6 @@ import { NO_TIMEOUT, SIGKILL, SIGTERM, Signals } from "../StartSdk" import { HealthReceipt } from "../health/HealthReceipt" -import { CheckResult } from "../health/checkFns" +import { HealthCheckResult } from "../health/checkFns" import { Trigger } from "../trigger" import { TriggerInput } from "../trigger/TriggerInput" @@ -23,7 +23,7 @@ export const cpExec = promisify(CP.exec) export const cpExecFile = promisify(CP.execFile) export type Ready = { display: string | null - fn: () => Promise | CheckResult + fn: () => Promise | HealthCheckResult trigger?: Trigger } diff --git a/sdk/lib/mainFn/HealthDaemon.ts b/sdk/lib/mainFn/HealthDaemon.ts index 84865e59b..7cb15cd42 100644 --- a/sdk/lib/mainFn/HealthDaemon.ts +++ b/sdk/lib/mainFn/HealthDaemon.ts @@ -1,8 +1,8 @@ -import { CheckResult } from "../health/checkFns" +import { HealthCheckResult } from "../health/checkFns" import { defaultTrigger } from "../trigger/defaultTrigger" import { Ready } from "./Daemons" import { Daemon } from "./Daemon" -import { Effects } from "../types" +import { Effects, SetHealth } from "../types" import { DEFAULT_SIGTERM_TIMEOUT } from "." const oncePromise = () => { @@ -21,10 +21,9 @@ const oncePromise = () => { * */ export class HealthDaemon { - #health: CheckResult = { status: "starting", message: null } + #health: HealthCheckResult = { result: "starting", message: null } #healthWatchers: Array<() => unknown> = [] #running = false - #hadSuccess = false constructor( readonly daemon: Promise, readonly daemonIndex: number, @@ -77,7 +76,7 @@ export class HealthDaemon { ;(await this.daemon).stop() this.turnOffHealthCheck() - this.setHealth({ status: "starting", message: null }) + this.setHealth({ result: "starting", message: null }) } } @@ -88,8 +87,7 @@ export class HealthDaemon { private async setupHealthCheck() { if (this.#healthCheckCleanup) return const trigger = (this.ready.trigger ?? defaultTrigger)(() => ({ - hadSuccess: this.#hadSuccess, - lastResult: this.#health.status, + lastResult: this.#health.result, })) const { promise: status, resolve: setStatus } = oncePromise<{ @@ -101,19 +99,16 @@ export class HealthDaemon { !res.done; res = await Promise.race([status, trigger.next()]) ) { - const response: CheckResult = await Promise.resolve( + const response: HealthCheckResult = await Promise.resolve( this.ready.fn(), ).catch((err) => { console.error(err) return { - status: "failure", + result: "failure", message: "message" in err ? err.message : String(err), } }) - this.setHealth(response) - if (response.status === "success") { - this.#hadSuccess = true - } + await this.setHealth(response) } }).catch((err) => console.error(`Daemon ${this.id} failed: ${err}`)) @@ -123,37 +118,23 @@ export class HealthDaemon { } } - private setHealth(health: CheckResult) { + private async setHealth(health: HealthCheckResult) { this.#health = health this.#healthWatchers.forEach((watcher) => watcher()) const display = this.ready.display - const status = health.status + const result = health.result if (!display) { return } - if ( - status === "success" || - status === "disabled" || - status === "starting" - ) { - this.effects.setHealth({ - result: status, - message: health.message, - id: this.id, - name: display, - }) - } else { - this.effects.setHealth({ - result: health.status, - message: health.message || "", - id: this.id, - name: display, - }) - } + await this.effects.setHealth({ + ...health, + id: this.id, + name: display, + } as SetHealth) } private async updateStatus() { const healths = this.dependencies.map((d) => d.#health) - this.changeRunning(healths.every((x) => x.status === "success")) + this.changeRunning(healths.every((x) => x.result === "success")) } } diff --git a/sdk/lib/osBindings/CheckDependenciesResult.ts b/sdk/lib/osBindings/CheckDependenciesResult.ts index d349bdf18..de58264bc 100644 --- a/sdk/lib/osBindings/CheckDependenciesResult.ts +++ b/sdk/lib/osBindings/CheckDependenciesResult.ts @@ -1,6 +1,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { HealthCheckId } from "./HealthCheckId" -import type { HealthCheckResult } from "./HealthCheckResult" +import type { NamedHealthCheckResult } from "./NamedHealthCheckResult" import type { PackageId } from "./PackageId" export type CheckDependenciesResult = { @@ -8,6 +8,6 @@ export type CheckDependenciesResult = { isInstalled: boolean isRunning: boolean configSatisfied: boolean - healthChecks: { [key: HealthCheckId]: HealthCheckResult } + healthChecks: { [key: HealthCheckId]: NamedHealthCheckResult } version: string | null } diff --git a/sdk/lib/osBindings/ExportServiceInterfaceParams.ts b/sdk/lib/osBindings/ExportServiceInterfaceParams.ts index b93e83f7c..28ac89916 100644 --- a/sdk/lib/osBindings/ExportServiceInterfaceParams.ts +++ b/sdk/lib/osBindings/ExportServiceInterfaceParams.ts @@ -8,7 +8,6 @@ export type ExportServiceInterfaceParams = { name: string description: string hasPrimary: boolean - disabled: boolean masked: boolean addressInfo: AddressInfo type: ServiceInterfaceType diff --git a/sdk/lib/osBindings/MainStatus.ts b/sdk/lib/osBindings/MainStatus.ts index 6acdce14a..a528aa187 100644 --- a/sdk/lib/osBindings/MainStatus.ts +++ b/sdk/lib/osBindings/MainStatus.ts @@ -1,6 +1,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { HealthCheckId } from "./HealthCheckId" -import type { HealthCheckResult } from "./HealthCheckResult" +import type { NamedHealthCheckResult } from "./NamedHealthCheckResult" export type MainStatus = | { status: "stopped" } @@ -11,10 +11,10 @@ export type MainStatus = | { status: "running" started: string - health: { [key: HealthCheckId]: HealthCheckResult } + health: { [key: HealthCheckId]: NamedHealthCheckResult } } | { status: "backingUp" started: string | null - health: { [key: HealthCheckId]: HealthCheckResult } + health: { [key: HealthCheckId]: NamedHealthCheckResult } } diff --git a/sdk/lib/osBindings/HealthCheckResult.ts b/sdk/lib/osBindings/NamedHealthCheckResult.ts similarity index 85% rename from sdk/lib/osBindings/HealthCheckResult.ts rename to sdk/lib/osBindings/NamedHealthCheckResult.ts index 6fa3d3f8c..c967e9b34 100644 --- a/sdk/lib/osBindings/HealthCheckResult.ts +++ b/sdk/lib/osBindings/NamedHealthCheckResult.ts @@ -1,6 +1,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type HealthCheckResult = { name: string } & ( +export type NamedHealthCheckResult = { name: string } & ( | { result: "success"; message: string | null } | { result: "disabled"; message: string | null } | { result: "starting"; message: string | null } diff --git a/sdk/lib/osBindings/ServiceInterface.ts b/sdk/lib/osBindings/ServiceInterface.ts index 91ac77515..9bcec0056 100644 --- a/sdk/lib/osBindings/ServiceInterface.ts +++ b/sdk/lib/osBindings/ServiceInterface.ts @@ -8,7 +8,6 @@ export type ServiceInterface = { name: string description: string hasPrimary: boolean - disabled: boolean masked: boolean addressInfo: AddressInfo type: ServiceInterfaceType diff --git a/sdk/lib/osBindings/index.ts b/sdk/lib/osBindings/index.ts index 2708aef8c..74baabfd9 100644 --- a/sdk/lib/osBindings/index.ts +++ b/sdk/lib/osBindings/index.ts @@ -69,7 +69,6 @@ export { Governor } from "./Governor" export { Guid } from "./Guid" export { HardwareRequirements } from "./HardwareRequirements" export { HealthCheckId } from "./HealthCheckId" -export { HealthCheckResult } from "./HealthCheckResult" export { HostAddress } from "./HostAddress" export { HostId } from "./HostId" export { HostKind } from "./HostKind" @@ -98,6 +97,7 @@ export { MaybeUtf8String } from "./MaybeUtf8String" export { MerkleArchiveCommitment } from "./MerkleArchiveCommitment" export { MountParams } from "./MountParams" export { MountTarget } from "./MountTarget" +export { NamedHealthCheckResult } from "./NamedHealthCheckResult" export { NamedProgress } from "./NamedProgress" export { OnionHostname } from "./OnionHostname" export { OsIndex } from "./OsIndex" diff --git a/sdk/lib/test/host.test.ts b/sdk/lib/test/host.test.ts index a8ae317ed..64d486a94 100644 --- a/sdk/lib/test/host.test.ts +++ b/sdk/lib/test/host.test.ts @@ -16,7 +16,6 @@ describe("host", () => { id: "foo", description: "A Foo", hasPrimary: false, - disabled: false, type: "ui", username: "bar", path: "/baz", diff --git a/sdk/lib/trigger/TriggerInput.ts b/sdk/lib/trigger/TriggerInput.ts index 9a52d8ca5..82fe79e07 100644 --- a/sdk/lib/trigger/TriggerInput.ts +++ b/sdk/lib/trigger/TriggerInput.ts @@ -2,5 +2,4 @@ import { HealthStatus } from "../types" export type TriggerInput = { lastResult?: HealthStatus - hadSuccess?: boolean } diff --git a/sdk/lib/trigger/changeOnFirstSuccess.ts b/sdk/lib/trigger/changeOnFirstSuccess.ts index 4c45afe31..3da7284df 100644 --- a/sdk/lib/trigger/changeOnFirstSuccess.ts +++ b/sdk/lib/trigger/changeOnFirstSuccess.ts @@ -5,10 +5,12 @@ export function changeOnFirstSuccess(o: { afterFirstSuccess: Trigger }): Trigger { return async function* (getInput) { - const beforeFirstSuccess = o.beforeFirstSuccess(getInput) - yield let currentValue = getInput() - beforeFirstSuccess.next() + while (!currentValue.lastResult) { + yield + currentValue = getInput() + } + const beforeFirstSuccess = o.beforeFirstSuccess(getInput) for ( let res = await beforeFirstSuccess.next(); currentValue?.lastResult !== "success" && !res.done; diff --git a/sdk/lib/trigger/defaultTrigger.ts b/sdk/lib/trigger/defaultTrigger.ts index bd52dc7cc..69cac2773 100644 --- a/sdk/lib/trigger/defaultTrigger.ts +++ b/sdk/lib/trigger/defaultTrigger.ts @@ -2,7 +2,7 @@ import { cooldownTrigger } from "./cooldownTrigger" import { changeOnFirstSuccess } from "./changeOnFirstSuccess" import { successFailure } from "./successFailure" -export const defaultTrigger = successFailure({ - duringSuccess: cooldownTrigger(0), - duringError: cooldownTrigger(30000), +export const defaultTrigger = changeOnFirstSuccess({ + beforeFirstSuccess: cooldownTrigger(1000), + afterFirstSuccess: cooldownTrigger(30000), }) diff --git a/sdk/lib/trigger/lastStatus.ts b/sdk/lib/trigger/lastStatus.ts new file mode 100644 index 000000000..90b8c9851 --- /dev/null +++ b/sdk/lib/trigger/lastStatus.ts @@ -0,0 +1,33 @@ +import { Trigger } from "." +import { HealthStatus } from "../types" + +export type LastStatusTriggerParams = { [k in HealthStatus]?: Trigger } & { + default: Trigger +} + +export function lastStatus(o: LastStatusTriggerParams): Trigger { + return async function* (getInput) { + let trigger = o.default(getInput) + const triggers: { + [k in HealthStatus]?: AsyncIterator + } & { default: AsyncIterator } = { + default: trigger, + } + while (true) { + let currentValue = getInput() + let prev: HealthStatus | "default" | undefined = currentValue.lastResult + if (!prev) { + yield + continue + } + if (!(prev in o)) { + prev = "default" + } + if (!triggers[prev]) { + triggers[prev] = o[prev]!(getInput) + } + await triggers[prev]?.next() + yield + } + } +} diff --git a/sdk/lib/trigger/successFailure.ts b/sdk/lib/trigger/successFailure.ts index 1bab27289..7febcd356 100644 --- a/sdk/lib/trigger/successFailure.ts +++ b/sdk/lib/trigger/successFailure.ts @@ -1,32 +1,7 @@ import { Trigger } from "." +import { lastStatus } from "./lastStatus" -export function successFailure(o: { +export const successFailure = (o: { duringSuccess: Trigger duringError: Trigger -}): Trigger { - return async function* (getInput) { - while (true) { - const beforeSuccess = o.duringSuccess(getInput) - yield - let currentValue = getInput() - beforeSuccess.next() - for ( - let res = await beforeSuccess.next(); - currentValue?.lastResult !== "success" && !res.done; - res = await beforeSuccess.next() - ) { - yield - currentValue = getInput() - } - const duringError = o.duringError(getInput) - for ( - let res = await duringError.next(); - currentValue?.lastResult === "success" && !res.done; - res = await duringError.next() - ) { - yield - currentValue = getInput() - } - } - } -} +}) => lastStatus({ success: o.duringSuccess, default: o.duringError }) diff --git a/sdk/lib/types.ts b/sdk/lib/types.ts index 9a3157ed3..1fdbcc66d 100644 --- a/sdk/lib/types.ts +++ b/sdk/lib/types.ts @@ -3,7 +3,7 @@ export * as configTypes from "./config/configTypes" import { DependencyRequirement, SetHealth, - HealthCheckResult, + NamedHealthCheckResult, SetMainStatus, ServiceInterface, Host, @@ -174,7 +174,7 @@ export type Daemon = { [DaemonProof]: never } -export type HealthStatus = HealthCheckResult["result"] +export type HealthStatus = NamedHealthCheckResult["result"] export type SmtpValue = { server: string port: number diff --git a/sdk/lib/util/Overlay.ts b/sdk/lib/util/Overlay.ts index 93cb44238..29ddadfb1 100644 --- a/sdk/lib/util/Overlay.ts +++ b/sdk/lib/util/Overlay.ts @@ -39,6 +39,23 @@ export class Overlay { return new Overlay(effects, id, rootfs, guid) } + static async with( + effects: T.Effects, + image: { id: T.ImageId; sharedRun?: boolean }, + mounts: { options: MountOptions; path: string }[], + fn: (overlay: Overlay) => Promise, + ): Promise { + const overlay = await Overlay.of(effects, image) + try { + for (let mount of mounts) { + await overlay.mount(mount.options, mount.path) + } + return await fn(overlay) + } finally { + await overlay.destroy() + } + } + async mount(options: MountOptions, path: string): Promise { path = path.startsWith("/") ? `${this.rootfs}${path}` diff --git a/sdk/lib/util/getServiceInterface.ts b/sdk/lib/util/getServiceInterface.ts index 9148c8f9a..fd0fef779 100644 --- a/sdk/lib/util/getServiceInterface.ts +++ b/sdk/lib/util/getServiceInterface.ts @@ -50,8 +50,6 @@ export type ServiceInterfaceFilled = { description: string /** Whether or not the interface has a primary URL */ hasPrimary: boolean - /** Whether or not the interface disabled */ - disabled: boolean /** Whether or not to mask the URIs for this interface. Useful if the URIs contain sensitive information, such as a password, macaroon, or API key */ masked: boolean /** Information about the host for this binding */ diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.ts b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.ts index fef84a5ba..f470bc43a 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.ts +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.ts @@ -10,15 +10,15 @@ import { ConnectionService } from 'src/app/services/connection.service' }) export class AppShowHealthChecksComponent { @Input() - healthChecks!: Record + healthChecks!: Record constructor(readonly connection$: ConnectionService) {} - isLoading(result: T.HealthCheckResult['result']): boolean { + isLoading(result: T.NamedHealthCheckResult['result']): boolean { return result === 'starting' || result === 'loading' } - isReady(result: T.HealthCheckResult['result']): boolean { + isReady(result: T.NamedHealthCheckResult['result']): boolean { return result !== 'failure' && result !== 'loading' } diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/pipes/health-color.pipe.ts b/web/projects/ui/src/app/pages/apps-routes/app-show/pipes/health-color.pipe.ts index 30d71d427..1f27b5e46 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-show/pipes/health-color.pipe.ts +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/pipes/health-color.pipe.ts @@ -5,7 +5,7 @@ import { T } from '@start9labs/start-sdk' name: 'healthColor', }) export class HealthColorPipe implements PipeTransform { - transform(val: T.HealthCheckResult['result']): string { + transform(val: T.NamedHealthCheckResult['result']): string { switch (val) { case 'success': return 'success' diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-health-checks.pipe.ts b/web/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-health-checks.pipe.ts index f66773f2c..24153caf9 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-health-checks.pipe.ts +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-health-checks.pipe.ts @@ -14,7 +14,7 @@ export class ToHealthChecksPipe implements PipeTransform { transform( manifest: T.Manifest, - ): Observable | null> { + ): Observable | null> { return this.patch.watch$('packageData', manifest.id, 'status', 'main').pipe( map(main => { return main.status === 'running' && !isEmptyObject(main.health) diff --git a/web/projects/ui/src/app/services/api/api.fixures.ts b/web/projects/ui/src/app/services/api/api.fixures.ts index d1ce9c47c..a7ae06193 100644 --- a/web/projects/ui/src/app/services/api/api.fixures.ts +++ b/web/projects/ui/src/app/services/api/api.fixures.ts @@ -1699,7 +1699,6 @@ export module Mock { ui: { id: 'ui', hasPrimary: false, - disabled: false, masked: false, name: 'Web UI', description: @@ -1717,7 +1716,6 @@ export module Mock { rpc: { id: 'rpc', hasPrimary: false, - disabled: false, masked: false, name: 'RPC', description: @@ -1735,7 +1733,6 @@ export module Mock { p2p: { id: 'p2p', hasPrimary: true, - disabled: false, masked: false, name: 'P2P', description: @@ -1876,7 +1873,6 @@ export module Mock { ui: { id: 'ui', hasPrimary: false, - disabled: false, masked: false, name: 'Web UI', description: 'A launchable web app for Bitcoin Proxy', @@ -1925,7 +1921,6 @@ export module Mock { grpc: { id: 'grpc', hasPrimary: false, - disabled: false, masked: false, name: 'GRPC', description: @@ -1943,7 +1938,6 @@ export module Mock { lndconnect: { id: 'lndconnect', hasPrimary: false, - disabled: false, masked: true, name: 'LND Connect', description: @@ -1961,7 +1955,6 @@ export module Mock { p2p: { id: 'p2p', hasPrimary: true, - disabled: false, masked: false, name: 'P2P', description: diff --git a/web/projects/ui/src/app/services/api/api.types.ts b/web/projects/ui/src/app/services/api/api.types.ts index 5742ba67f..ef0e80d20 100644 --- a/web/projects/ui/src/app/services/api/api.types.ts +++ b/web/projects/ui/src/app/services/api/api.types.ts @@ -535,7 +535,7 @@ export interface DependencyErrorConfigUnsatisfied { export interface DependencyErrorHealthChecksFailed { type: 'healthChecksFailed' - check: T.HealthCheckResult + check: T.NamedHealthCheckResult } export interface DependencyErrorTransitive { diff --git a/web/projects/ui/src/app/services/api/mock-patch.ts b/web/projects/ui/src/app/services/api/mock-patch.ts index 8b4c28d1e..5a6c7b815 100644 --- a/web/projects/ui/src/app/services/api/mock-patch.ts +++ b/web/projects/ui/src/app/services/api/mock-patch.ts @@ -132,7 +132,6 @@ export const mockPatchData: DataModel = { ui: { id: 'ui', hasPrimary: false, - disabled: false, masked: false, name: 'Web UI', description: @@ -150,7 +149,6 @@ export const mockPatchData: DataModel = { rpc: { id: 'rpc', hasPrimary: false, - disabled: false, masked: false, name: 'RPC', description: @@ -168,7 +166,6 @@ export const mockPatchData: DataModel = { p2p: { id: 'p2p', hasPrimary: true, - disabled: false, masked: false, name: 'P2P', description: @@ -311,7 +308,6 @@ export const mockPatchData: DataModel = { grpc: { id: 'grpc', hasPrimary: false, - disabled: false, masked: false, name: 'GRPC', description: @@ -329,7 +325,6 @@ export const mockPatchData: DataModel = { lndconnect: { id: 'lndconnect', hasPrimary: false, - disabled: false, masked: true, name: 'LND Connect', description: @@ -347,7 +342,6 @@ export const mockPatchData: DataModel = { p2p: { id: 'p2p', hasPrimary: true, - disabled: false, masked: false, name: 'P2P', description: