mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +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:
@@ -256,6 +256,18 @@ export class HostSystemStartOs implements Effects {
|
||||
T.Effects["setDependencies"]
|
||||
>
|
||||
}
|
||||
checkDependencies(
|
||||
options: Parameters<T.Effects["checkDependencies"]>[0],
|
||||
): ReturnType<T.Effects["checkDependencies"]> {
|
||||
return this.rpcRound("checkDependencies", options) as ReturnType<
|
||||
T.Effects["checkDependencies"]
|
||||
>
|
||||
}
|
||||
getDependencies(): ReturnType<T.Effects["getDependencies"]> {
|
||||
return this.rpcRound("getDependencies", null) as ReturnType<
|
||||
T.Effects["getDependencies"]
|
||||
>
|
||||
}
|
||||
setHealth(...[options]: Parameters<T.Effects["setHealth"]>) {
|
||||
return this.rpcRound("setHealth", options) as ReturnType<
|
||||
T.Effects["setHealth"]
|
||||
|
||||
4
core/Cargo.lock
generated
4
core/Cargo.lock
generated
@@ -3121,9 +3121,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
|
||||
@@ -3,8 +3,10 @@ use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, TS)]
|
||||
#[ts(type = "string")]
|
||||
pub struct Version {
|
||||
version: emver::Version,
|
||||
string: String,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
ValidIfNoStupidEscape,
|
||||
} from "./types"
|
||||
import * as patterns from "./util/patterns"
|
||||
import { DependencyConfig, Update } from "./dependencyConfig/DependencyConfig"
|
||||
import { DependencyConfig, Update } from "./dependencies/DependencyConfig"
|
||||
import { BackupSet, Backups } from "./backup/Backups"
|
||||
import { smtpConfig } from "./config/configConstants"
|
||||
import { Daemons } from "./mainFn/Daemons"
|
||||
@@ -35,7 +35,7 @@ import { List } from "./config/builder/list"
|
||||
import { Migration } from "./inits/migrations/Migration"
|
||||
import { Install, InstallFn } from "./inits/setupInstall"
|
||||
import { setupActions } from "./actions/setupActions"
|
||||
import { setupDependencyConfig } from "./dependencyConfig/setupDependencyConfig"
|
||||
import { setupDependencyConfig } from "./dependencies/setupDependencyConfig"
|
||||
import { SetupBackupsParams, setupBackups } from "./backup/setupBackups"
|
||||
import { setupInit } from "./inits/setupInit"
|
||||
import {
|
||||
@@ -77,6 +77,7 @@ import * as T from "./types"
|
||||
import { Checker, EmVer } from "./emverLite/mod"
|
||||
import { ExposedStorePaths } from "./store/setupExposeStore"
|
||||
import { PathBuilder, extractJsonPath, pathBuilder } from "./store/PathBuilder"
|
||||
import { checkAllDependencies } from "./dependencies/dependencies"
|
||||
|
||||
// prettier-ignore
|
||||
type AnyNeverCond<T extends any[], Then, Else> =
|
||||
@@ -124,6 +125,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
}
|
||||
|
||||
return {
|
||||
checkAllDependencies,
|
||||
serviceInterface: {
|
||||
getOwn: <E extends Effects>(effects: E, id: ServiceInterfaceId) =>
|
||||
removeConstType<E>()(
|
||||
@@ -284,7 +286,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
||||
>(
|
||||
spec: ConfigType,
|
||||
write: Save<Store, Type, Manifest>,
|
||||
write: Save<Type>,
|
||||
read: Read<Manifest, Store, Type>,
|
||||
) => setupConfig<Store, ConfigType, Manifest, Type>(spec, write, read),
|
||||
setupConfigRead: <
|
||||
@@ -301,7 +303,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
| Config<Record<string, never>, never>,
|
||||
>(
|
||||
_configSpec: ConfigSpec,
|
||||
fn: Save<Store, ConfigSpec, Manifest>,
|
||||
fn: Save<ConfigSpec>,
|
||||
) => fn,
|
||||
setupDependencyConfig: <Input extends Record<string, any>>(
|
||||
config: Config<Input, Store> | Config<Input, never>,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||
import { Dependency } from "../types"
|
||||
import { Dependencies } from "../types"
|
||||
|
||||
export type ConfigDependencies<T extends SDKManifest> = {
|
||||
exists(id: keyof T["dependencies"]): Dependency
|
||||
running(id: keyof T["dependencies"], healthChecks: string[]): Dependency
|
||||
exists(id: keyof T["dependencies"]): Dependencies[number]
|
||||
running(
|
||||
id: keyof T["dependencies"],
|
||||
healthChecks: string[],
|
||||
): Dependencies[number]
|
||||
}
|
||||
|
||||
export const configDependenciesSet = <
|
||||
@@ -13,7 +16,7 @@ export const configDependenciesSet = <
|
||||
return {
|
||||
id,
|
||||
kind: "exists",
|
||||
} as Dependency
|
||||
} as Dependencies[number]
|
||||
},
|
||||
|
||||
running(id: keyof T["dependencies"], healthChecks: string[]) {
|
||||
@@ -21,6 +24,6 @@ export const configDependenciesSet = <
|
||||
id,
|
||||
kind: "running",
|
||||
healthChecks,
|
||||
} as Dependency
|
||||
} as Dependencies[number]
|
||||
},
|
||||
})
|
||||
|
||||
@@ -11,16 +11,13 @@ export type DependenciesReceipt = void & {
|
||||
}
|
||||
|
||||
export type Save<
|
||||
Store,
|
||||
A extends
|
||||
| Record<string, any>
|
||||
| Config<Record<string, any>, any>
|
||||
| Config<Record<string, never>, never>,
|
||||
Manifest extends SDKManifest,
|
||||
> = (options: {
|
||||
effects: Effects
|
||||
input: ExtractConfigType<A> & Record<string, any>
|
||||
dependencies: D.ConfigDependencies<Manifest>
|
||||
}) => Promise<{
|
||||
dependenciesReceipt: DependenciesReceipt
|
||||
interfacesReceipt: InterfacesReceipt
|
||||
@@ -53,7 +50,7 @@ export function setupConfig<
|
||||
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
||||
>(
|
||||
spec: Config<Type, Store> | Config<Type, never>,
|
||||
write: Save<Store, Type, Manifest>,
|
||||
write: Save<Type>,
|
||||
read: Read<Manifest, Store, Type>,
|
||||
) {
|
||||
const validator = spec.validator
|
||||
@@ -66,9 +63,8 @@ export function setupConfig<
|
||||
await effects.clearBindings()
|
||||
await effects.clearServiceInterfaces()
|
||||
const { restart } = await write({
|
||||
input: JSON.parse(JSON.stringify(input)),
|
||||
input: JSON.parse(JSON.stringify(input)) as any,
|
||||
effects,
|
||||
dependencies: D.configDependenciesSet<Manifest>(),
|
||||
})
|
||||
if (restart) {
|
||||
await effects.restart()
|
||||
|
||||
115
sdk/lib/dependencies/dependencies.ts
Normal file
115
sdk/lib/dependencies/dependencies.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import {
|
||||
Effects,
|
||||
PackageId,
|
||||
DependencyRequirement,
|
||||
SetHealth,
|
||||
CheckDependencyResult,
|
||||
} from "../types"
|
||||
|
||||
export type CheckAllDependencies = {
|
||||
notRunning: () => Promise<CheckDependencyResult[]>
|
||||
|
||||
notInstalled: () => Promise<CheckDependencyResult[]>
|
||||
|
||||
healthErrors: () => Promise<{ [id: string]: SetHealth[] }>
|
||||
throwIfNotRunning: () => Promise<void>
|
||||
throwIfNotValid: () => Promise<undefined>
|
||||
throwIfNotInstalled: () => Promise<void>
|
||||
throwIfError: () => Promise<void>
|
||||
isValid: () => Promise<boolean>
|
||||
}
|
||||
export function checkAllDependencies(effects: Effects): CheckAllDependencies {
|
||||
const dependenciesPromise = effects.getDependencies()
|
||||
const resultsPromise = dependenciesPromise.then((dependencies) =>
|
||||
effects.checkDependencies({
|
||||
packageIds: dependencies.map((dep) => dep.id),
|
||||
}),
|
||||
)
|
||||
|
||||
const dependenciesByIdPromise = dependenciesPromise.then((d) =>
|
||||
d.reduce(
|
||||
(acc, dep) => {
|
||||
acc[dep.id] = dep
|
||||
return acc
|
||||
},
|
||||
{} as { [id: PackageId]: DependencyRequirement },
|
||||
),
|
||||
)
|
||||
|
||||
const healthErrors = async () => {
|
||||
const results = await resultsPromise
|
||||
const dependenciesById = await dependenciesByIdPromise
|
||||
const answer: { [id: PackageId]: SetHealth[] } = {}
|
||||
for (const result of results) {
|
||||
const dependency = dependenciesById[result.packageId]
|
||||
if (!dependency) continue
|
||||
if (dependency.kind !== "running") continue
|
||||
|
||||
const healthChecks = result.healthChecks
|
||||
.filter((x) => dependency.healthChecks.includes(x.id))
|
||||
.filter((x) => !!x.message)
|
||||
if (healthChecks.length === 0) continue
|
||||
answer[result.packageId] = healthChecks
|
||||
}
|
||||
return answer
|
||||
}
|
||||
const notInstalled = () =>
|
||||
resultsPromise.then((x) => x.filter((x) => !x.isInstalled))
|
||||
const notRunning = async () => {
|
||||
const results = await resultsPromise
|
||||
const dependenciesById = await dependenciesByIdPromise
|
||||
return results.filter((x) => {
|
||||
const dependency = dependenciesById[x.packageId]
|
||||
if (!dependency) return false
|
||||
if (dependency.kind !== "running") return false
|
||||
return !x.isRunning
|
||||
})
|
||||
}
|
||||
const entries = <B>(x: { [k: string]: B }) => Object.entries(x)
|
||||
const first = <A>(x: A[]): A | undefined => x[0]
|
||||
const sinkVoid = <A>(x: A) => void 0
|
||||
const throwIfError = () =>
|
||||
healthErrors()
|
||||
.then(entries)
|
||||
.then(first)
|
||||
.then((x) => {
|
||||
if (!x) return
|
||||
const [id, healthChecks] = x
|
||||
if (healthChecks.length > 0)
|
||||
throw `Package ${id} has the following errors: ${healthChecks.map((x) => x.message).join(", ")}`
|
||||
})
|
||||
const throwIfNotRunning = () =>
|
||||
notRunning().then((results) => {
|
||||
if (results[0])
|
||||
throw new Error(`Package ${results[0].packageId} is not running`)
|
||||
})
|
||||
|
||||
const throwIfNotInstalled = () =>
|
||||
notInstalled().then((results) => {
|
||||
if (results[0])
|
||||
throw new Error(`Package ${results[0].packageId} is not installed`)
|
||||
})
|
||||
const throwIfNotValid = async () =>
|
||||
Promise.all([
|
||||
throwIfNotRunning(),
|
||||
throwIfNotInstalled(),
|
||||
throwIfError(),
|
||||
]).then(sinkVoid)
|
||||
|
||||
const isValid = () =>
|
||||
throwIfNotValid().then(
|
||||
() => true,
|
||||
() => false,
|
||||
)
|
||||
|
||||
return {
|
||||
notRunning,
|
||||
notInstalled,
|
||||
healthErrors,
|
||||
throwIfNotRunning,
|
||||
throwIfNotValid,
|
||||
throwIfNotInstalled,
|
||||
throwIfError,
|
||||
isValid,
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { TriggerInput } from "../trigger/TriggerInput"
|
||||
import { defaultTrigger } from "../trigger/defaultTrigger"
|
||||
import { once } from "../util/once"
|
||||
import { Overlay } from "../util/Overlay"
|
||||
import { object, unknown } from "ts-matches"
|
||||
|
||||
export function healthCheck(o: {
|
||||
effects: Effects
|
||||
@@ -66,8 +67,7 @@ export function healthCheck(o: {
|
||||
return {} as HealthReceipt
|
||||
}
|
||||
function asMessage(e: unknown) {
|
||||
if (typeof e === "object" && e != null && "message" in e)
|
||||
return String(e.message)
|
||||
if (object({ message: unknown }).test(e)) return String(e.message)
|
||||
const value = String(e)
|
||||
if (value.length == null) return null
|
||||
return value
|
||||
|
||||
@@ -4,7 +4,7 @@ export { setupExposeStore } from "./store/setupExposeStore"
|
||||
export * as config from "./config"
|
||||
export * as CB from "./config/builder"
|
||||
export * as CT from "./config/configTypes"
|
||||
export * as dependencyConfig from "./dependencyConfig"
|
||||
export * as dependencyConfig from "./dependencies"
|
||||
export * as manifest from "./manifest"
|
||||
export * as types from "./types"
|
||||
export * as T from "./types"
|
||||
|
||||
@@ -12,7 +12,7 @@ export * as backup from "./backup"
|
||||
export * as config from "./config"
|
||||
export * as CB from "./config/builder"
|
||||
export * as CT from "./config/configTypes"
|
||||
export * as dependencyConfig from "./dependencyConfig"
|
||||
export * as dependencyConfig from "./dependencies"
|
||||
export * as daemons from "./mainFn/Daemons"
|
||||
export * as health from "./health"
|
||||
export * as healthFns from "./health/checkFns"
|
||||
|
||||
@@ -161,7 +161,7 @@ export class Host {
|
||||
options: BindOptionsByKnownProtocol,
|
||||
protoInfo: KnownProtocols[keyof KnownProtocols],
|
||||
): AddSslOptions | null {
|
||||
if ("noAddSsl" in options && options.noAddSsl) return null
|
||||
if (inObject("noAddSsl", options) && options.noAddSsl) return null
|
||||
if ("withSsl" in protoInfo && protoInfo.withSsl)
|
||||
return {
|
||||
// addXForwardedHeaders: null,
|
||||
@@ -174,6 +174,13 @@ export class Host {
|
||||
}
|
||||
}
|
||||
|
||||
function inObject<Key extends string>(
|
||||
key: Key,
|
||||
obj: any,
|
||||
): obj is { [K in Key]: unknown } {
|
||||
return key in obj
|
||||
}
|
||||
|
||||
export class StaticHost extends Host {
|
||||
constructor(options: { effects: Effects; id: string }) {
|
||||
super({ ...options, kind: "static" })
|
||||
|
||||
4
sdk/lib/osBindings/CheckDependenciesParam.ts
Normal file
4
sdk/lib/osBindings/CheckDependenciesParam.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type CheckDependenciesParam = { packageIds: Array<PackageId> | null }
|
||||
11
sdk/lib/osBindings/CheckDependenciesResult.ts
Normal file
11
sdk/lib/osBindings/CheckDependenciesResult.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { HealthCheckResult } from "./HealthCheckResult"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type CheckDependenciesResult = {
|
||||
packageId: PackageId
|
||||
isInstalled: boolean
|
||||
isRunning: boolean
|
||||
healthChecks: Array<HealthCheckResult>
|
||||
version: string | null
|
||||
}
|
||||
@@ -12,6 +12,8 @@ export { BindInfo } from "./BindInfo"
|
||||
export { BindOptions } from "./BindOptions"
|
||||
export { BindParams } from "./BindParams"
|
||||
export { Callback } from "./Callback"
|
||||
export { CheckDependenciesParam } from "./CheckDependenciesParam"
|
||||
export { CheckDependenciesResult } from "./CheckDependenciesResult"
|
||||
export { ChrootParams } from "./ChrootParams"
|
||||
export { CreateOverlayedImageParams } from "./CreateOverlayedImageParams"
|
||||
export { CurrentDependencies } from "./CurrentDependencies"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Effects } from "../types"
|
||||
import { ExecuteAction } from ".././osBindings"
|
||||
import { CheckDependenciesParam, ExecuteAction } from ".././osBindings"
|
||||
import { CreateOverlayedImageParams } from ".././osBindings"
|
||||
import { DestroyOverlayedImageParams } from ".././osBindings"
|
||||
import { BindParams } from ".././osBindings"
|
||||
@@ -64,6 +64,8 @@ describe("startosTypeValidation ", () => {
|
||||
removeAction: {} as RemoveActionParams,
|
||||
reverseProxy: {} as ReverseProxyParams,
|
||||
mount: {} as MountParams,
|
||||
checkDependencies: {} as CheckDependenciesParam,
|
||||
getDependencies: undefined,
|
||||
})
|
||||
typeEquality<Parameters<Effects["executeAction"]>[0]>(
|
||||
testInput as ExecuteAction,
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
export * as configTypes from "./config/configTypes"
|
||||
import { HealthCheckId } from "./osBindings"
|
||||
import { HealthCheckResult } from "./osBindings"
|
||||
|
||||
import {
|
||||
DependencyRequirement,
|
||||
SetHealth,
|
||||
HealthCheckResult,
|
||||
} from "./osBindings"
|
||||
|
||||
import { MainEffects, ServiceInterfaceType, Signals } from "./StartSdk"
|
||||
import { InputSpec } from "./config/configTypes"
|
||||
import { DependenciesReceipt } from "./config/setupConfig"
|
||||
@@ -11,6 +16,7 @@ import { ExposedStorePaths } from "./store/setupExposeStore"
|
||||
import { UrlString } from "./util/getServiceInterface"
|
||||
export * from "./osBindings"
|
||||
export { SDKManifest } from "./manifest/ManifestTypes"
|
||||
export { HealthReceipt } from "./health/HealthReceipt"
|
||||
|
||||
export type ExportedAction = (options: {
|
||||
effects: Effects
|
||||
@@ -471,16 +477,22 @@ export type Effects = {
|
||||
algorithm: "ecdsa" | "ed25519" | null
|
||||
}) => Promise<string>
|
||||
|
||||
setHealth(
|
||||
o: HealthCheckResult & {
|
||||
id: HealthCheckId
|
||||
},
|
||||
): Promise<void>
|
||||
setHealth(o: SetHealth): Promise<void>
|
||||
|
||||
/** Set the dependencies of what the service needs, usually ran during the set config as a best practice */
|
||||
setDependencies(options: {
|
||||
dependencies: Dependencies
|
||||
}): Promise<DependenciesReceipt>
|
||||
|
||||
/** Get the list of the dependencies, both the dynamic set by the effect of setDependencies and the end result any required in the manifest */
|
||||
getDependencies(): Promise<DependencyRequirement[]>
|
||||
|
||||
/** When one wants to checks the status of several services during the checking of dependencies. The result will include things like the status
|
||||
* of the service and what the current health checks are.
|
||||
*/
|
||||
checkDependencies(options: {
|
||||
packageIds: PackageId[] | null
|
||||
}): Promise<CheckDependencyResult[]>
|
||||
/** Exists could be useful during the runtime to know if some service exists, option dep */
|
||||
exists(options: { packageId: PackageId }): Promise<boolean>
|
||||
/** Exists could be useful during the runtime to know if some service is running, option dep */
|
||||
@@ -578,13 +590,17 @@ export type KnownError =
|
||||
errorCode: [number, string] | readonly [number, string]
|
||||
}
|
||||
|
||||
export type Dependency = {
|
||||
id: PackageId
|
||||
versionSpec: string
|
||||
registryUrl: string
|
||||
} & ({ kind: "exists" } | { kind: "running"; healthChecks: string[] })
|
||||
export type Dependencies = Array<Dependency>
|
||||
export type Dependencies = Array<DependencyRequirement>
|
||||
|
||||
export type DeepPartial<T> = T extends {}
|
||||
? { [P in keyof T]?: DeepPartial<T[P]> }
|
||||
: T
|
||||
|
||||
export type CheckDependencyResult = {
|
||||
packageId: PackageId
|
||||
isInstalled: boolean
|
||||
isRunning: boolean
|
||||
healthChecks: SetHealth[]
|
||||
version: string | null
|
||||
}
|
||||
export type CheckResults = CheckDependencyResult[]
|
||||
|
||||
@@ -77,7 +77,7 @@ export class Overlay {
|
||||
stdout: string | Buffer
|
||||
stderr: string | Buffer
|
||||
}> {
|
||||
const imageMeta = await fs
|
||||
const imageMeta: any = await fs
|
||||
.readFile(`/media/startos/images/${this.imageId}.json`, {
|
||||
encoding: "utf8",
|
||||
})
|
||||
@@ -147,7 +147,7 @@ export class Overlay {
|
||||
command: string[],
|
||||
options?: CommandOptions,
|
||||
): Promise<cp.ChildProcessWithoutNullStreams> {
|
||||
const imageMeta = await fs
|
||||
const imageMeta: any = await fs
|
||||
.readFile(`/media/startos/images/${this.imageId}.json`, {
|
||||
encoding: "utf8",
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user