chore: Making sure that the values that we are returning are valid now with the new types

This commit is contained in:
J H
2024-03-25 12:01:13 -06:00
parent fba1484e2e
commit 299d9998ad
4 changed files with 55 additions and 362 deletions

View File

@@ -147,11 +147,6 @@ export class HostSystemStartOs implements Effects {
T.Effects["exposeForDependents"]
>
}
exposeUi(...[options]: Parameters<T.Effects["exposeUi"]>) {
return this.rpcRound("exposeUi", options) as ReturnType<
T.Effects["exposeUi"]
>
}
getConfigured(...[]: Parameters<T.Effects["getConfigured"]>) {
return this.rpcRound("getConfigured", null) as ReturnType<
T.Effects["getConfigured"]

View File

@@ -25,15 +25,12 @@ export class MainLoop {
wait: Promise<unknown>
}>
| undefined
private propertiesEvent: NodeJS.Timeout | undefined
constructor(
readonly system: SystemForEmbassy,
readonly effects: HostSystemStartOs,
readonly runProperties: () => Promise<void>,
) {
this.healthLoops = this.constructHealthLoops()
this.mainEvent = this.constructMainEvent()
this.propertiesEvent = this.constructPropertiesEvent()
}
private async constructMainEvent() {
@@ -85,23 +82,14 @@ export class MainLoop {
}
public async clean(options?: { timeout?: number }) {
const { mainEvent, healthLoops, propertiesEvent } = this
const { mainEvent, healthLoops } = this
const main = await mainEvent
delete this.mainEvent
delete this.healthLoops
delete this.propertiesEvent
if (mainEvent) await main?.daemon.term()
clearInterval(propertiesEvent)
if (healthLoops) healthLoops.forEach((x) => clearInterval(x.interval))
}
private constructPropertiesEvent() {
const { runProperties } = this
return setInterval(() => {
runProperties()
}, EMBASSY_PROPERTIES_LOOP)
}
private constructHealthLoops() {
const { manifest } = this.system
const effects = this.effects

View File

@@ -59,6 +59,33 @@ export type PackagePropertyObject = {
type: "object"
description: string
}
const asProperty_ = (
x: PackagePropertyString | PackagePropertyObject,
): T.PropertiesValue => {
if (x.type === "object") {
return {
...x,
value: Object.fromEntries(
Object.entries(x.value).map(([key, value]) => [
key,
asProperty_(value),
]),
),
}
}
return {
masked: false,
description: null,
qr: null,
copyable: null,
...x,
}
}
const asProperty = (x: PackagePropertiesV2): T.PropertiesReturn =>
Object.fromEntries(
Object.entries(x).map(([key, value]) => [key, asProperty_(value)]),
)
const [matchPackageProperties, setMatchPackageProperties] =
deferred<PackagePropertiesV2>()
const matchPackagePropertyObject: Parser<unknown, PackagePropertyObject> =
@@ -92,44 +119,6 @@ const matchProperties = object({
data: matchPackageProperties,
})
type ExportUi = {
values: { [key: string]: any }
expose: { [key: string]: T.ExposeUiPathsAll }
}
function propertiesToExportUi(
properties: PackagePropertiesV2,
previousPath = "",
): ExportUi {
const exportUi: ExportUi = {
values: {},
expose: {},
}
for (const [key, value] of Object.entries(properties)) {
const path = `${previousPath}/${key}`
if (value.type === "object") {
const { values, expose } = propertiesToExportUi(value.value, path)
exportUi.values[key] = values
exportUi.expose[key] = {
type: "object",
value: expose,
description: value.description,
}
continue
}
exportUi.values[key] = value.value
exportUi.expose[key] = {
type: "string",
path,
description: value.description ?? null,
masked: value.masked ?? false,
copyable: value.copyable ?? null,
qr: value.qr ?? null,
}
}
return exportUi
}
export class SystemForEmbassy implements System {
currentRunning: MainLoop | undefined
static async of(manifestLocation: string = MANIFEST_LOCATION) {
@@ -236,6 +225,8 @@ export class SystemForEmbassy implements System {
return this.getConfig(effects)
case "/config/set":
return this.setConfig(effects, input)
case "/properties":
return this.properties(effects)
case "/actions/metadata":
return todo()
case "/init":
@@ -279,9 +270,7 @@ export class SystemForEmbassy implements System {
private async mainStart(effects: HostSystemStartOs): Promise<void> {
if (!!this.currentRunning) return
this.currentRunning = new MainLoop(this, effects, () =>
this.properties(effects),
)
this.currentRunning = new MainLoop(this, effects)
}
private async mainStop(
effects: HostSystemStartOs,
@@ -472,51 +461,44 @@ export class SystemForEmbassy implements System {
}
return { configured: true }
}
private async properties(effects: HostSystemStartOs): Promise<undefined> {
private async properties(
effects: HostSystemStartOs,
): Promise<ReturnType<T.ExpectedExports.Properties>> {
// TODO BLU-J set the properties ever so often
const setConfigValue = this.manifest.properties
if (!setConfigValue) return
if (!setConfigValue) throw new Error("There is no properties")
if (setConfigValue.type === "docker") {
const container = await DockerProcedureContainer.of(
effects,
setConfigValue,
this.manifest.volumes,
)
const properties = JSON.parse(
(
await container.exec([
setConfigValue.entrypoint,
...setConfigValue.args,
])
).stdout.toString(),
const properties = matchProperties.unsafeCast(
JSON.parse(
(
await container.exec([
setConfigValue.entrypoint,
...setConfigValue.args,
])
).stdout.toString(),
),
)
if (!matchProperties.test(properties)) return
const exposeUis = propertiesToExportUi(properties.data)
await effects.store.set<any, any>({
path: "/properties",
value: exposeUis.values,
})
await effects.exposeUi(exposeUis.expose)
return asProperty(properties.data)
} else if (setConfigValue.type === "script") {
const moduleCode = this.moduleCode
const method = moduleCode.properties
if (!method)
throw new Error("Expecting that the method properties exists")
const properties = await method(
new 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])
})
if (!matchProperties.test(properties)) return
const exposeUis = propertiesToExportUi(properties.data)
await effects.store.set<any, any>({
path: "/properties",
value: exposeUis.values,
})
await effects.exposeUi(exposeUis.expose)
const properties = matchProperties.unsafeCast(
await method(new 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])
}),
)
return asProperty(properties.data)
}
throw new Error(`Unknown type in the fetch properties: ${setConfigValue}`)
}
private async health(
effects: HostSystemStartOs,
@@ -651,279 +633,6 @@ export class SystemForEmbassy implements System {
throw new Error("Error getting config: " + x["error-code"][1])
})) as any
}
// private async sandbox(
// effects: HostSystemStartOs,
// options: {
// procedure:
// | "/createBackup"
// | "/restoreBackup"
// | "/getConfig"
// | "/setConfig"
// | "migration"
// | "/properties"
// | `/action/${string}`
// | `/dependencies/${string}/check`
// | `/dependencies/${string}/autoConfigure`
// input: unknown
// timeout?: number | undefined
// },
// ): Promise<unknown> {
// const input = options.input
// switch (options.procedure) {
// case "/createBackup":
// return this.roCreateBackup(effects)
// case "/restoreBackup":
// return this.roRestoreBackup(effects)
// case "/getConfig":
// return this.roGetConfig(effects)
// case "/setConfig":
// return this.roSetConfig(effects, input)
// case "migration":
// return this.roMigration(effects, input)
// case "/properties":
// return this.roProperties(effects)
// default:
// const procedure = options.procedure.split("/")
// switch (true) {
// case options.procedure.startsWith("/action/"):
// return this.roAction(effects, procedure[2], input)
// case options.procedure.startsWith("/dependencies/") &&
// procedure[3] === "check":
// return this.roDependenciesCheck(effects, procedure[2], input)
// case options.procedure.startsWith("/dependencies/") &&
// procedure[3] === "autoConfigure":
// return this.roDependenciesAutoconfig(effects, procedure[2], input)
// }
// }
// }
// private async roCreateBackup(effects: HostSystemStartOs): Promise<void> {
// const backup = this.manifest.backup.create
// if (backup.type === "docker") {
// const container = await DockerProcedureContainer.readonlyOf(backup)
// await container.exec([backup.entrypoint, ...backup.args])
// } else {
// const moduleCode = await this.moduleCode
// await moduleCode.createBackup?.(new PolyfillEffects(effects))
// }
// }
// private async roRestoreBackup(effects: HostSystemStartOs): Promise<void> {
// const restoreBackup = this.manifest.backup.restore
// if (restoreBackup.type === "docker") {
// const container = await DockerProcedureContainer.readonlyOf(restoreBackup)
// await container.exec([restoreBackup.entrypoint, ...restoreBackup.args])
// } else {
// const moduleCode = await this.moduleCode
// await moduleCode.restoreBackup?.(new PolyfillEffects(effects))
// }
// }
// private async roGetConfig(effects: HostSystemStartOs): Promise<T.ConfigRes> {
// const config = this.manifest.config?.get
// if (!config) return { spec: {} }
// if (config.type === "docker") {
// const container = await DockerProcedureContainer.readonlyOf(config)
// return JSON.parse(
// (await container.exec([config.entrypoint, ...config.args])).stdout,
// )
// } else {
// const moduleCode = await this.moduleCode
// const method = moduleCode.getConfig
// if (!method) throw new Error("Expecting that the method getConfig exists")
// return (await method(new PolyfillEffects(effects)).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])
// })) as any
// }
// }
// private async roSetConfig(
// effects: HostSystemStartOs,
// newConfig: unknown,
// ): Promise<T.SetResult> {
// const setConfigValue = this.manifest.config?.set
// if (!setConfigValue) return { signal: "SIGTERM", "depends-on": {} }
// if (setConfigValue.type === "docker") {
// const container = await DockerProcedureContainer.readonlyOf(
// setConfigValue,
// )
// return JSON.parse(
// (
// await container.exec([
// setConfigValue.entrypoint,
// ...setConfigValue.args,
// JSON.stringify(newConfig),
// ])
// ).stdout,
// )
// } else {
// const moduleCode = await this.moduleCode
// const method = moduleCode.setConfig
// if (!method) throw new Error("Expecting that the method setConfig exists")
// return await method(
// new PolyfillEffects(effects),
// newConfig as U.Config,
// ).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])
// })
// }
// }
// private async roMigration(
// effects: HostSystemStartOs,
// fromVersion: unknown,
// ): Promise<T.MigrationRes> {
// throw new Error("Migrations should never be ran in the sandbox mode")
// }
// private async roProperties(effects: HostSystemStartOs): Promise<unknown> {
// const setConfigValue = this.manifest.properties
// if (!setConfigValue) return {}
// if (setConfigValue.type === "docker") {
// const container = await DockerProcedureContainer.readonlyOf(
// setConfigValue,
// )
// return JSON.parse(
// (
// await container.exec([
// setConfigValue.entrypoint,
// ...setConfigValue.args,
// ])
// ).stdout,
// )
// } else {
// const moduleCode = await this.moduleCode
// const method = moduleCode.properties
// if (!method)
// throw new Error("Expecting that the method properties exists")
// return await method(new PolyfillEffects(effects)).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])
// })
// }
// }
// private async roHealth(
// effects: HostSystemStartOs,
// healthId: string,
// timeSinceStarted: unknown,
// ): Promise<void> {
// const healthProcedure = this.manifest["health-checks"][healthId]
// if (!healthProcedure) return
// if (healthProcedure.type === "docker") {
// const container = await DockerProcedureContainer.readonlyOf(
// healthProcedure,
// )
// return JSON.parse(
// (
// await container.exec([
// healthProcedure.entrypoint,
// ...healthProcedure.args,
// JSON.stringify(timeSinceStarted),
// ])
// ).stdout,
// )
// } else {
// const moduleCode = await this.moduleCode
// const method = moduleCode.health?.[healthId]
// if (!method) throw new Error("Expecting that the method health exists")
// await method(new PolyfillEffects(effects), Number(timeSinceStarted)).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])
// },
// )
// }
// }
// private async roAction(
// effects: HostSystemStartOs,
// actionId: string,
// formData: unknown,
// ): Promise<T.ActionResult> {
// const actionProcedure = this.manifest.actions?.[actionId]?.implementation
// if (!actionProcedure) return { message: "Action not found", value: null }
// if (actionProcedure.type === "docker") {
// const container = await DockerProcedureContainer.readonlyOf(
// actionProcedure,
// )
// return JSON.parse(
// (
// await container.exec([
// actionProcedure.entrypoint,
// ...actionProcedure.args,
// JSON.stringify(formData),
// ])
// ).stdout,
// )
// } 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(new PolyfillEffects(effects), formData as any).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])
// },
// )) as any
// }
// }
// private async roDependenciesCheck(
// effects: HostSystemStartOs,
// id: string,
// oldConfig: unknown,
// ): Promise<object> {
// const actionProcedure = this.manifest.dependencies?.[id]?.config?.check
// if (!actionProcedure) return { message: "Action not found", value: null }
// if (actionProcedure.type === "docker") {
// const container = await DockerProcedureContainer.readonlyOf(
// actionProcedure,
// )
// return JSON.parse(
// (
// await container.exec([
// actionProcedure.entrypoint,
// ...actionProcedure.args,
// JSON.stringify(oldConfig),
// ])
// ).stdout,
// )
// } else {
// const moduleCode = await this.moduleCode
// const method = moduleCode.dependencies?.[id]?.check
// if (!method)
// throw new Error(
// `Expecting that the method dependency check ${id} exists`,
// )
// return (await method(new PolyfillEffects(effects), oldConfig as any).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])
// },
// )) as any
// }
// }
// private async roDependenciesAutoconfig(
// effects: HostSystemStartOs,
// id: string,
// oldConfig: unknown,
// ): Promise<void> {
// const moduleCode = await this.moduleCode
// const method = moduleCode.dependencies?.[id]?.autoConfigure
// if (!method)
// throw new Error(
// `Expecting that the method dependency autoConfigure ${id} exists`,
// )
// return (await method(new PolyfillEffects(effects), oldConfig as any).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])
// },
// )) as any
// }
}
async function removePointers(value: T.ConfigRes): Promise<T.ConfigRes> {
const startingSpec = structuredClone(value.spec)

View File

@@ -35,6 +35,7 @@ export const jsonPath = some(
"/backup/create",
"/backup/restore",
"/actions/metadata",
"/properties",
),
string.refine(isNestedPath, "isNestedPath"),
)