sdk updates

This commit is contained in:
Aiden McClelland
2024-08-08 11:10:02 -06:00
parent bd7adafee0
commit 058bfe0737
41 changed files with 190 additions and 221 deletions

View File

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

View File

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

View File

@@ -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<HealthCheckId, HealthCheckResult>,
health_checks: BTreeMap<HealthCheckId, NamedHealthCheckResult>,
#[ts(type = "string | null")]
version: Option<exver::ExtendedVersion>,
}

View File

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

View File

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

View File

@@ -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<HealthCheckId, HealthCheckResult>,
health: OrdMap<HealthCheckId, NamedHealthCheckResult>,
started: DateTime<Utc>,
}

View File

@@ -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<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 {
"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<Self>;
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<String> },
Disabled { message: Option<String> },
Starting { message: Option<String> },
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})")
}
}
}
}

View File

@@ -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<Utc>,
#[ts(as = "BTreeMap<HealthCheckId, HealthCheckResult>")]
health: OrdMap<HealthCheckId, HealthCheckResult>,
#[ts(as = "BTreeMap<HealthCheckId, NamedHealthCheckResult>")]
health: OrdMap<HealthCheckId, NamedHealthCheckResult>,
},
#[serde(rename_all = "camelCase")]
BackingUp {
#[ts(type = "string | null")]
started: Option<DateTime<Utc>>,
#[ts(as = "BTreeMap<HealthCheckId, HealthCheckResult>")]
health: OrdMap<HealthCheckId, HealthCheckResult>,
#[ts(as = "BTreeMap<HealthCheckId, NamedHealthCheckResult>")]
health: OrdMap<HealthCheckId, NamedHealthCheckResult>,
},
}
impl MainStatus {
@@ -93,7 +95,7 @@ impl MainStatus {
MainStatus::BackingUp { started, health }
}
pub fn health(&self) -> Option<&OrdMap<HealthCheckId, HealthCheckResult>> {
pub fn health(&self) -> Option<&OrdMap<HealthCheckId, NamedHealthCheckResult>> {
match self {
MainStatus::Running { health, .. } => Some(health),
MainStatus::BackingUp { health, .. } => Some(health),

View File

@@ -293,8 +293,8 @@ export class StartSdk<Manifest extends T.Manifest, Store> {
)
},
HealthCheck: {
of(o: HealthCheckParams<Manifest>) {
return healthCheck<Manifest>(o)
of(o: HealthCheckParams) {
return healthCheck(o)
},
},
Dependency: {

View File

@@ -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<Manifest extends T.Manifest> = {
export type HealthCheckParams = {
effects: Effects
name: string
image: {
id: keyof Manifest["images"] & T.ImageId
sharedRun?: boolean
}
trigger?: Trigger
fn(overlay: Overlay): Promise<CheckResult> | CheckResult
fn(): Promise<HealthCheckResult> | HealthCheckResult
onFirstSuccess?: () => unknown | Promise<unknown>
}
export function healthCheck<Manifest extends T.Manifest>(
o: HealthCheckParams<Manifest>,
) {
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

View File

@@ -1,6 +0,0 @@
import { HealthStatus } from "../../types"
export type CheckResult = {
status: HealthStatus
message: string | null
}

View File

@@ -0,0 +1,3 @@
import { T } from "../.."
export type HealthCheckResult = Omit<T.NamedHealthCheckResult, "name">

View File

@@ -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<CheckResult> {
return Promise.race<CheckResult>([
): Promise<HealthCheckResult> {
return Promise.race<HealthCheckResult>([
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}`,
}),

View File

@@ -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<CheckResult> => {
): Promise<HealthCheckResult> => {
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 }
})
}

View File

@@ -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" } = {}) {

View File

@@ -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<CheckResult> => {
): Promise<HealthCheckResult> => {
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
}

View File

@@ -47,7 +47,6 @@ export class Origin<T extends Host> {
name,
description,
hasPrimary,
disabled,
id,
type,
username,
@@ -69,7 +68,6 @@ export class Origin<T extends Host> {
name,
description,
hasPrimary,
disabled,
addressInfo,
type,
masked,

View File

@@ -21,7 +21,6 @@ export class ServiceInterfaceBuilder {
id: string
description: string
hasPrimary: boolean
disabled: boolean
type: ServiceInterfaceType
username: string | null
path: string

View File

@@ -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> | CheckResult
fn: () => Promise<HealthCheckResult> | HealthCheckResult
trigger?: Trigger
}

View File

@@ -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 = <T>() => {
@@ -21,10 +21,9 @@ const oncePromise = <T>() => {
*
*/
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<Daemon>,
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"))
}
}

View File

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

View File

@@ -8,7 +8,6 @@ export type ExportServiceInterfaceParams = {
name: string
description: string
hasPrimary: boolean
disabled: boolean
masked: boolean
addressInfo: AddressInfo
type: ServiceInterfaceType

View File

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

View File

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

View File

@@ -8,7 +8,6 @@ export type ServiceInterface = {
name: string
description: string
hasPrimary: boolean
disabled: boolean
masked: boolean
addressInfo: AddressInfo
type: ServiceInterfaceType

View File

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

View File

@@ -16,7 +16,6 @@ describe("host", () => {
id: "foo",
description: "A Foo",
hasPrimary: false,
disabled: false,
type: "ui",
username: "bar",
path: "/baz",

View File

@@ -2,5 +2,4 @@ import { HealthStatus } from "../types"
export type TriggerInput = {
lastResult?: HealthStatus
hadSuccess?: boolean
}

View File

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

View File

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

View File

@@ -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<unknown, unknown, never>
} & { default: AsyncIterator<unknown, unknown, never> } = {
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
}
}
}

View File

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

View File

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

View File

@@ -39,6 +39,23 @@ export class Overlay {
return new Overlay(effects, id, rootfs, guid)
}
static async with<T>(
effects: T.Effects,
image: { id: T.ImageId; sharedRun?: boolean },
mounts: { options: MountOptions; path: string }[],
fn: (overlay: Overlay) => Promise<T>,
): Promise<T> {
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<Overlay> {
path = path.startsWith("/")
? `${this.rootfs}${path}`

View File

@@ -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 */

View File

@@ -10,15 +10,15 @@ import { ConnectionService } from 'src/app/services/connection.service'
})
export class AppShowHealthChecksComponent {
@Input()
healthChecks!: Record<string, T.HealthCheckResult>
healthChecks!: Record<string, T.NamedHealthCheckResult>
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'
}

View File

@@ -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'

View File

@@ -14,7 +14,7 @@ export class ToHealthChecksPipe implements PipeTransform {
transform(
manifest: T.Manifest,
): Observable<Record<string, T.HealthCheckResult | null> | null> {
): Observable<Record<string, T.NamedHealthCheckResult | null> | null> {
return this.patch.watch$('packageData', manifest.id, 'status', 'main').pipe(
map(main => {
return main.status === 'running' && !isEmptyObject(main.health)

View File

@@ -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:

View File

@@ -535,7 +535,7 @@ export interface DependencyErrorConfigUnsatisfied {
export interface DependencyErrorHealthChecksFailed {
type: 'healthChecksFailed'
check: T.HealthCheckResult
check: T.NamedHealthCheckResult
}
export interface DependencyErrorTransitive {

View File

@@ -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: