From 4d7694de24cc0f6a5fe7c40a48d0894e01d896ac Mon Sep 17 00:00:00 2001
From: Jade <2364004+Blu-J@users.noreply.github.com>
Date: Mon, 19 Aug 2024 21:38:05 -0600
Subject: [PATCH] chore: reimplement refactor for the changes (#2716)
* chore: reimplement refactor for the changes
* chore: Make it so even more cases are caught on the transformation
* Update container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts
* chore: Update the types of the action result because it wasnt matching what was in the action.rs
---
container-runtime/src/Adapters/RpcListener.ts | 132 ++++++--
.../Systems/SystemForEmbassy/index.ts | 315 +++++++++---------
.../src/Adapters/Systems/SystemForStartOs.ts | 253 ++++++--------
container-runtime/src/Interfaces/System.ts | 63 +++-
sdk/lib/types.ts | 9 +-
5 files changed, 419 insertions(+), 353 deletions(-)
diff --git a/container-runtime/src/Adapters/RpcListener.ts b/container-runtime/src/Adapters/RpcListener.ts
index 28f578149..860f1c066 100644
--- a/container-runtime/src/Adapters/RpcListener.ts
+++ b/container-runtime/src/Adapters/RpcListener.ts
@@ -19,7 +19,7 @@ import * as fs from "fs"
import { CallbackHolder } from "../Models/CallbackHolder"
import { AllGetDependencies } from "../Interfaces/AllGetDependencies"
-import { jsonPath } from "../Models/JsonPath"
+import { jsonPath, unNestPath } from "../Models/JsonPath"
import { RunningMain, System } from "../Interfaces/System"
import {
MakeMainEffects,
@@ -52,6 +52,8 @@ const SOCKET_PARENT = "/media/startos/rpc"
const SOCKET_PATH = "/media/startos/rpc/service.sock"
const jsonrpc = "2.0" as const
+const isResult = object({ result: any }).test
+
const idType = some(string, number, literal(null))
type IdType = null | string | number
const runType = object({
@@ -64,7 +66,7 @@ const runType = object({
input: any,
timeout: number,
},
- ["timeout", "input"],
+ ["timeout"],
),
})
const sandboxRunType = object({
@@ -77,7 +79,7 @@ const sandboxRunType = object({
input: any,
timeout: number,
},
- ["timeout", "input"],
+ ["timeout"],
),
})
const callbackType = object({
@@ -226,27 +228,25 @@ export class RpcListener {
const system = this.system
const procedure = jsonPath.unsafeCast(params.procedure)
const effects = this.getDependencies.makeProcedureEffects()(params.id)
- return handleRpc(
- id,
- system.execute(effects, {
- procedure,
- input: params.input,
- timeout: params.timeout,
- }),
- )
+ const input = params.input
+ const timeout = params.timeout
+ const result = getResult(procedure, system, effects, timeout, input)
+
+ return handleRpc(id, result)
})
.when(sandboxRunType, async ({ id, params }) => {
const system = this.system
const procedure = jsonPath.unsafeCast(params.procedure)
const effects = this.makeProcedureEffects(params.id)
- return handleRpc(
- id,
- system.sandbox(effects, {
- procedure,
- input: params.input,
- timeout: params.timeout,
- }),
+ const result = getResult(
+ procedure,
+ system,
+ effects,
+ params.input,
+ params.input,
)
+
+ return handleRpc(id, result)
})
.when(callbackType, async ({ params: { callback, args } }) => {
this.system.callCallback(callback, args)
@@ -280,7 +280,7 @@ export class RpcListener {
(async () => {
if (!this._system) {
const system = await this.getDependencies.system()
- await system.init()
+ await system.containerInit()
this._system = system
}
})().then((result) => ({ result })),
@@ -342,3 +342,97 @@ export class RpcListener {
})
}
}
+function getResult(
+ procedure: typeof jsonPath._TYPE,
+ system: System,
+ effects: T.Effects,
+ timeout: number | undefined,
+ input: any,
+) {
+ const ensureResultTypeShape = (
+ result:
+ | void
+ | T.ConfigRes
+ | T.PropertiesReturn
+ | T.ActionMetadata[]
+ | T.ActionResult,
+ ): { result: any } => {
+ if (isResult(result)) return result
+ return { result }
+ }
+ return (async () => {
+ switch (procedure) {
+ case "/backup/create":
+ return system.createBackup(effects, timeout || null)
+ case "/backup/restore":
+ return system.restoreBackup(effects, timeout || null)
+ case "/config/get":
+ return system.getConfig(effects, timeout || null)
+ case "/config/set":
+ return system.setConfig(effects, input, timeout || null)
+ case "/properties":
+ return system.properties(effects, timeout || null)
+ case "/actions/metadata":
+ return system.actionsMetadata(effects)
+ case "/init":
+ return system.packageInit(
+ effects,
+ string.optional().unsafeCast(input),
+ timeout || null,
+ )
+ case "/uninit":
+ return system.packageUninit(
+ effects,
+ string.optional().unsafeCast(input),
+ timeout || null,
+ )
+ default:
+ const procedures = unNestPath(procedure)
+ switch (true) {
+ case procedures[1] === "actions" && procedures[3] === "get":
+ return system.action(effects, procedures[2], input, timeout || null)
+ case procedures[1] === "actions" && procedures[3] === "run":
+ return system.action(effects, procedures[2], input, timeout || null)
+ case procedures[1] === "dependencies" && procedures[3] === "query":
+ return system.dependenciesAutoconfig(
+ effects,
+ procedures[2],
+ input,
+ timeout || null,
+ )
+
+ case procedures[1] === "dependencies" && procedures[3] === "update":
+ return system.dependenciesAutoconfig(
+ effects,
+ procedures[2],
+ input,
+ timeout || null,
+ )
+ }
+ }
+ })().then(ensureResultTypeShape, (error) =>
+ matches(error)
+ .when(
+ object(
+ {
+ error: string,
+ code: number,
+ },
+ ["code"],
+ { code: 0 },
+ ),
+ (error) => ({
+ error: {
+ code: error.code,
+ message: error.error,
+ },
+ }),
+ )
+ .defaultToLazy(() => ({
+ error: {
+ code: 0,
+ message: String(error),
+ },
+ })),
+ )
+}
diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts
index 131d912e1..cee873c21 100644
--- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts
+++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts
@@ -61,6 +61,42 @@ const MANIFEST_LOCATION = "/usr/lib/startos/package/embassyManifest.json"
export const EMBASSY_JS_LOCATION = "/usr/lib/startos/package/embassy.js"
const EMBASSY_POINTER_PATH_PREFIX = "/embassyConfig" as StorePath
+const matchResult = object({
+ result: any,
+})
+const matchError = object({
+ error: string,
+})
+const matchErrorCode = object<{
+ "error-code": [number, string] | readonly [number, string]
+}>({
+ "error-code": tuple(number, string),
+})
+
+const assertNever = (
+ x: never,
+ message = "Not expecting to get here: ",
+): never => {
+ throw new Error(message + JSON.stringify(x))
+}
+/**
+ Should be changing the type for specific properties, and this is mostly a transformation for the old return types to the newer one.
+*/
+const fromReturnType = (a: U.ResultType): A => {
+ if (matchResult.test(a)) {
+ return a.result
+ }
+ if (matchError.test(a)) {
+ console.info({ passedErrorStack: new Error().stack, error: a.error })
+ throw { error: a.error }
+ }
+ if (matchErrorCode.test(a)) {
+ const [code, message] = a["error-code"]
+ throw { error: message, code }
+ }
+ return assertNever(a)
+}
+
const matchSetResult = object(
{
"depends-on": dictionary([string, array(string)]),
@@ -206,12 +242,49 @@ export class SystemForEmbassy implements System {
moduleCode,
)
}
+
constructor(
readonly manifest: Manifest,
readonly moduleCode: Partial,
) {}
- async init(): Promise {}
+ async actionsMetadata(effects: T.Effects): Promise {
+ const actions = Object.entries(this.manifest.actions ?? {})
+ return Promise.all(
+ actions.map(async ([actionId, action]): Promise => {
+ const name = action.name ?? actionId
+ const description = action.description
+ const warning = action.warning ?? null
+ const disabled = false
+ const input = (await convertToNewConfig(action["input-spec"] as any))
+ .spec
+ const hasRunning = !!action["allowed-statuses"].find(
+ (x) => x === "running",
+ )
+ const hasStopped = !!action["allowed-statuses"].find(
+ (x) => x === "stopped",
+ )
+ // prettier-ignore
+ const allowedStatuses =
+ hasRunning && hasStopped ? "any":
+ hasRunning ? "onlyRunning" :
+ "onlyStopped"
+
+ const group = null
+ return {
+ name,
+ description,
+ warning,
+ disabled,
+ allowedStatuses,
+ group,
+ input,
+ }
+ }),
+ )
+ }
+
+ async containerInit(): Promise {}
async exit(): Promise {
if (this.currentRunning) await this.currentRunning.clean()
@@ -235,141 +308,7 @@ export class SystemForEmbassy implements System {
}
}
- async execute(
- effects: Effects,
- options: {
- procedure: JsonPath
- input?: unknown
- timeout?: number | undefined
- },
- ): Promise {
- return this._execute(effects, options)
- .then((x) =>
- matches(x)
- .when(
- object({
- result: any,
- }),
- (x) => x,
- )
- .when(
- object({
- error: string,
- }),
- (x) => ({
- error: {
- code: 0,
- message: x.error,
- },
- }),
- )
- .when(
- object({
- "error-code": tuple(number, string),
- }),
- ({ "error-code": [code, message] }) => ({
- error: {
- code,
- message,
- },
- }),
- )
- .defaultTo({ result: x }),
- )
- .catch((error: unknown) => {
- if (error instanceof Error)
- return {
- error: {
- code: 0,
- message: error.name,
- data: {
- details: error.message,
- debug: `${error?.cause ?? "[noCause]"}:${error?.stack ?? "[noStack]"}`,
- },
- },
- }
- if (matchRpcResult.test(error)) return error
- return {
- error: {
- code: 0,
- message: String(error),
- },
- }
- })
- }
- async _execute(
- effects: Effects,
- options: {
- procedure: JsonPath
- input?: unknown
- timeout?: number | undefined
- },
- ): Promise {
- const input = options.input
- switch (options.procedure) {
- case "/backup/create":
- return this.createBackup(effects, options.timeout || null)
- case "/backup/restore":
- return this.restoreBackup(effects, options.timeout || null)
- case "/config/get":
- return this.getConfig(effects, options.timeout || null)
- case "/config/set":
- return this.setConfig(effects, input, options.timeout || null)
- case "/properties":
- return this.properties(effects, options.timeout || null)
- case "/actions/metadata":
- return todo()
- case "/init":
- return this.initProcedure(
- effects,
- string.optional().unsafeCast(input),
- options.timeout || null,
- )
- case "/uninit":
- return this.uninit(
- effects,
- string.optional().unsafeCast(input),
- options.timeout || null,
- )
- default:
- const procedures = unNestPath(options.procedure)
- switch (true) {
- case procedures[1] === "actions" && procedures[3] === "get":
- return this.action(
- effects,
- procedures[2],
- input,
- options.timeout || null,
- )
- case procedures[1] === "actions" && procedures[3] === "run":
- return this.action(
- effects,
- procedures[2],
- input,
- options.timeout || null,
- )
- case procedures[1] === "dependencies" && procedures[3] === "query":
- return null
-
- case procedures[1] === "dependencies" && procedures[3] === "update":
- return this.dependenciesAutoconfig(
- effects,
- procedures[2],
- input,
- options.timeout || null,
- )
- }
- }
- throw new Error(`Could not find the path for ${options.procedure}`)
- }
- async sandbox(
- effects: Effects,
- options: { procedure: Procedure; input: unknown; timeout?: number },
- ): Promise {
- return this.execute(effects, options)
- }
-
- private async initProcedure(
+ async packageInit(
effects: Effects,
previousVersion: Optional,
timeoutMs: number | null,
@@ -489,7 +428,7 @@ export class SystemForEmbassy implements System {
})
}
}
- private async uninit(
+ async packageUninit(
effects: Effects,
nextVersion: Optional,
timeoutMs: number | null,
@@ -498,7 +437,7 @@ export class SystemForEmbassy implements System {
await effects.setMainStatus({ status: "stopped" })
}
- private async createBackup(
+ async createBackup(
effects: Effects,
timeoutMs: number | null,
): Promise {
@@ -519,7 +458,7 @@ export class SystemForEmbassy implements System {
await moduleCode.createBackup?.(polyfillEffects(effects, this.manifest))
}
}
- private async restoreBackup(
+ async restoreBackup(
effects: Effects,
timeoutMs: number | null,
): Promise {
@@ -543,7 +482,7 @@ export class SystemForEmbassy implements System {
await moduleCode.restoreBackup?.(polyfillEffects(effects, this.manifest))
}
}
- private async getConfig(
+ async getConfig(
effects: Effects,
timeoutMs: number | null,
): Promise {
@@ -584,7 +523,7 @@ export class SystemForEmbassy implements System {
)) as any
}
}
- private async setConfig(
+ async setConfig(
effects: Effects,
newConfigWithoutPointers: unknown,
timeoutMs: number | null,
@@ -676,7 +615,7 @@ export class SystemForEmbassy implements System {
})
}
- private async migration(
+ async migration(
effects: Effects,
fromVersion: string,
timeoutMs: number | null,
@@ -748,10 +687,10 @@ export class SystemForEmbassy implements System {
}
return { configured: true }
}
- private async properties(
+ async properties(
effects: Effects,
timeoutMs: number | null,
- ): Promise> {
+ ): Promise {
// TODO BLU-J set the properties ever so often
const setConfigValue = this.manifest.properties
if (!setConfigValue) throw new Error("There is no properties")
@@ -779,36 +718,81 @@ export class SystemForEmbassy implements System {
if (!method)
throw new Error("Expecting that the method properties exists")
const properties = matchProperties.unsafeCast(
- await method(polyfillEffects(effects, this.manifest)).then((x) => {
- if ("result" in x) return x.result
- if ("error" in x) throw new Error("Error getting config: " + x.error)
- throw new Error("Error getting config: " + x["error-code"][1])
- }),
+ await method(polyfillEffects(effects, this.manifest)).then(
+ fromReturnType,
+ ),
)
return asProperty(properties.data)
}
throw new Error(`Unknown type in the fetch properties: ${setConfigValue}`)
}
- private async action(
+ async action(
effects: Effects,
actionId: string,
formData: unknown,
timeoutMs: number | null,
): Promise {
const actionProcedure = this.manifest.actions?.[actionId]?.implementation
- if (!actionProcedure) return { message: "Action not found", value: null }
+ const toActionResult = ({
+ message,
+ value = "",
+ copyable,
+ qr,
+ }: U.ActionResult): T.ActionResult => ({
+ version: "0",
+ message,
+ value,
+ copyable,
+ qr,
+ })
+ if (!actionProcedure) throw Error("Action not found")
+ if (actionProcedure.type === "docker") {
+ const container = await DockerProcedureContainer.of(
+ effects,
+ this.manifest.id,
+ actionProcedure,
+ this.manifest.volumes,
+ )
+ return toActionResult(
+ JSON.parse(
+ (
+ await container.execFail(
+ [
+ actionProcedure.entrypoint,
+ ...actionProcedure.args,
+ JSON.stringify(formData),
+ ],
+ timeoutMs,
+ )
+ ).stdout.toString(),
+ ),
+ )
+ } else {
+ const moduleCode = await this.moduleCode
+ const method = moduleCode.action?.[actionId]
+ if (!method) throw new Error("Expecting that the method action exists")
+ return await method(
+ polyfillEffects(effects, this.manifest),
+ formData as any,
+ )
+ .then(fromReturnType)
+ .then(toActionResult)
+ }
+ }
+ async dependenciesCheck(
+ effects: Effects,
+ id: string,
+ oldConfig: unknown,
+ timeoutMs: number | null,
+ ): Promise