Feature/sdk040dependencies (#2609)

* update registry upload to take id for new admin permissions (#2605)

* wip

* wip: Get the get dependencies

* wip check_dependencies

* wip: Get the build working to the vm

* wip: Add in the last of the things that where needed for the new sdk

* Add fix

* wip: implement the changes

* wip: Fix the naming

---------

Co-authored-by: Lucy <12953208+elvece@users.noreply.github.com>
This commit is contained in:
Jade
2024-04-26 17:51:33 -06:00
committed by GitHub
parent e08d93b2aa
commit 8a38666105
24 changed files with 417 additions and 39 deletions

View File

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

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

View File

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

View File

@@ -74,12 +74,14 @@ async fn do_upload(
mut url: Url,
user: &str,
pass: &str,
pkg_id: &str,
body: Body,
) -> Result<(), Error> {
url.set_path("/admin/v0/upload");
let req = httpc
.post(url)
.header(header::ACCEPT, "text/plain")
.query(&["id", pkg_id])
.basic_auth(user, Some(pass))
.body(body)
.build()?;
@@ -198,6 +200,7 @@ pub async fn publish(
registry.clone(),
&user,
&pass,
&pkg.id,
Body::wrap_stream(file_stream),
)
.await?;

View File

@@ -11,6 +11,7 @@ use clap::Parser;
use emver::VersionRange;
use imbl::OrdMap;
use imbl_value::{json, InternedString};
use itertools::Itertools;
use models::{
ActionId, DataUrl, HealthCheckId, HostId, Id, ImageId, PackageId, ServiceInterfaceId, VolumeId,
};
@@ -23,6 +24,7 @@ use url::Url;
use crate::db::model::package::{
ActionMetadata, CurrentDependencies, CurrentDependencyInfo, CurrentDependencyKind,
ManifestPreference,
};
use crate::disk::mount::filesystem::idmapped::IdMapped;
use crate::disk::mount::filesystem::loop_dev::LoopDev;
@@ -154,6 +156,18 @@ pub fn service_effect_handler() -> ParentHandler {
.no_display()
.with_remote_cli::<ContainerCliContext>(),
)
.subcommand(
"getDependencies",
from_fn_async(get_dependencies)
.no_display()
.with_remote_cli::<ContainerCliContext>(),
)
.subcommand(
"checkDependencies",
from_fn_async(check_dependencies)
.no_display()
.with_remote_cli::<ContainerCliContext>(),
)
.subcommand("getSystemSmtp", from_fn_async(get_system_smtp).no_cli())
.subcommand("getContainerIp", from_fn_async(get_container_ip).no_cli())
.subcommand(
@@ -178,6 +192,7 @@ pub fn service_effect_handler() -> ParentHandler {
.subcommand("removeAction", from_fn_async(remove_action).no_cli())
.subcommand("reverseProxy", from_fn_async(reverse_proxy).no_cli())
.subcommand("mount", from_fn_async(mount).no_cli())
// TODO Callbacks
}
@@ -1270,3 +1285,138 @@ async fn set_dependencies(
})
.await
}
async fn get_dependencies(ctx: EffectContext) -> Result<Vec<DependencyRequirement>, Error> {
let ctx = ctx.deref()?;
let id = &ctx.id;
let db = ctx.ctx.db.peek().await;
let data = db
.as_public()
.as_package_data()
.as_idx(id)
.or_not_found(id)?
.as_current_dependencies()
.de()?;
data.0
.into_iter()
.map(|(id, current_dependency_info)| {
let CurrentDependencyInfo {
registry_url,
version_spec,
kind,
..
} = current_dependency_info;
Ok::<_, Error>(match kind {
CurrentDependencyKind::Exists => DependencyRequirement::Exists {
id,
registry_url,
version_spec,
},
CurrentDependencyKind::Running { health_checks } => {
DependencyRequirement::Running {
id,
health_checks,
version_spec,
registry_url,
}
}
})
})
.try_collect()
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Parser, TS)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "camelCase")]
#[ts(export)]
struct CheckDependenciesParam {
package_ids: Option<Vec<PackageId>>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
struct CheckDependenciesResult {
package_id: PackageId,
is_installed: bool,
is_running: bool,
health_checks: Vec<HealthCheckResult>,
#[ts(type = "string | null")]
version: Option<emver::Version>,
}
async fn check_dependencies(
ctx: EffectContext,
CheckDependenciesParam { package_ids }: CheckDependenciesParam,
) -> Result<Vec<CheckDependenciesResult>, Error> {
let ctx = ctx.deref()?;
let db = ctx.ctx.db.peek().await;
let current_dependencies = db
.as_public()
.as_package_data()
.as_idx(&ctx.id)
.or_not_found(&ctx.id)?
.as_current_dependencies()
.de()?;
let package_ids: Vec<_> = package_ids
.unwrap_or_else(|| current_dependencies.0.keys().cloned().collect())
.into_iter()
.filter_map(|x| {
let info = current_dependencies.0.get(&x)?;
Some((x, info))
})
.collect();
let mut results = Vec::with_capacity(package_ids.len());
for (package_id, dependency_info) in package_ids {
let Some(package) = db.as_public().as_package_data().as_idx(&package_id) else {
results.push(CheckDependenciesResult {
package_id,
is_installed: false,
is_running: false,
health_checks: vec![],
version: None,
});
continue;
};
let installed_version = package
.as_state_info()
.as_manifest(ManifestPreference::New)
.as_version()
.de()?
.into_version();
let version = Some(installed_version.clone());
if !installed_version.satisfies(&dependency_info.version_spec) {
results.push(CheckDependenciesResult {
package_id,
is_installed: false,
is_running: false,
health_checks: vec![],
version,
});
continue;
}
let is_installed = true;
let status = package.as_status().as_main().de()?;
let is_running = if is_installed {
status.running()
} else {
false
};
let health_checks = status
.health()
.cloned()
.unwrap_or_default()
.into_iter()
.map(|(_, val)| val)
.collect();
results.push(CheckDependenciesResult {
package_id,
is_installed,
is_running,
health_checks,
version,
});
}
Ok(results)
}

View File

@@ -1,7 +1,12 @@
use std::str::FromStr;
use clap::builder::ValueParserFactory;
pub use models::HealthCheckId;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use crate::util::clap::FromStrParser;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, TS)]
#[serde(rename_all = "camelCase")]
pub struct HealthCheckResult {
@@ -9,6 +14,45 @@ pub struct HealthCheckResult {
#[serde(flatten)]
pub kind: HealthCheckResultKind,
}
// healthCheckName:kind:message OR healthCheckName:kind
impl FromStr for HealthCheckResult {
type Err = color_eyre::eyre::Report;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let from_parts = |name: &str, kind: &str, message: Option<&str>| {
let message = message.map(|x| x.to_string());
let kind = match kind {
"success" => HealthCheckResultKind::Success { message },
"disabled" => HealthCheckResultKind::Disabled { message },
"starting" => HealthCheckResultKind::Starting { message },
"loading" => HealthCheckResultKind::Loading {
message: message.unwrap_or_default(),
},
"failure" => HealthCheckResultKind::Failure {
message: message.unwrap_or_default(),
},
_ => return Err(color_eyre::eyre::eyre!("Invalid health check kind")),
};
Ok(Self {
name: name.to_string(),
kind,
})
};
let parts = s.split(':').collect::<Vec<_>>();
match &*parts {
[name, kind, message] => from_parts(name, kind, Some(message)),
[name, kind] => from_parts(name, kind, None),
_ => Err(color_eyre::eyre::eyre!(
"Could not match the shape of the result ${parts:?}"
)),
}
}
}
impl ValueParserFactory for HealthCheckResult {
type Parser = FromStrParser<Self>;
fn value_parser() -> Self::Parser {
FromStrParser::new()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, TS)]
#[serde(rename_all = "camelCase")]

View File

@@ -91,4 +91,13 @@ impl MainStatus {
};
MainStatus::BackingUp { started, health }
}
pub fn health(&self) -> Option<&OrdMap<HealthCheckId, HealthCheckResult>> {
match self {
MainStatus::Running { health, .. } => Some(health),
MainStatus::BackingUp { health, .. } => Some(health),
MainStatus::Stopped | MainStatus::Stopping { .. } | MainStatus::Restarting => None,
MainStatus::Starting { .. } => None,
}
}
}

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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