From 9156d899033bbfd5d016b24097b032975e308ca1 Mon Sep 17 00:00:00 2001 From: Blu-J Date: Thu, 8 Jun 2023 14:54:14 -0600 Subject: [PATCH] feat: Change the commands --- lib/StartSdk.ts | 6 +- lib/backup/Backups.ts | 42 ++--- lib/dependencyConfig/DependencyConfig.ts | 54 +++---- lib/health/checkFns/checkPortListening.ts | 9 +- lib/health/checkFns/checkWebUrl.ts | 3 +- lib/health/checkFns/runHealthScript.ts | 8 +- lib/mainFn/Daemons.ts | 6 +- lib/types.ts | 15 +- lib/util/utils.ts | 181 ++++++++++++++-------- package-lock.json | 85 ++++++++++ package.json | 1 + 11 files changed, 270 insertions(+), 140 deletions(-) diff --git a/lib/StartSdk.ts b/lib/StartSdk.ts index 00f06fc..636bcff 100644 --- a/lib/StartSdk.ts +++ b/lib/StartSdk.ts @@ -22,7 +22,7 @@ import { } from "./types" import * as patterns from "./util/patterns" import { Utils } from "./util/utils" -import { DependencyConfig } from "./dependencyConfig/DependencyConfig" +import { DependencyConfig, Update } from "./dependencyConfig/DependencyConfig" import { BackupSet, Backups } from "./backup/Backups" import { smtpConfig } from "./config/configConstants" import { Daemons } from "./mainFn/Daemons" @@ -231,18 +231,20 @@ export class StartSdk { localConfig, remoteConfig, dependencyConfig, + update, }: { localConfig: Config | Config remoteConfig: Config | Config dependencyConfig: (options: { effects: Effects localConfig: LocalConfig - remoteConfig: RemoteConfig utils: Utils }) => Promise> + update?: Update, RemoteConfig> }) { return new DependencyConfig( dependencyConfig, + update, ) }, }, diff --git a/lib/backup/Backups.ts b/lib/backup/Backups.ts index 0e1f4cb..9df5304 100644 --- a/lib/backup/Backups.ts +++ b/lib/backup/Backups.ts @@ -134,15 +134,15 @@ export class Backups { // path: item.dstPath, // }) // } - await effects - .runRsync({ - ...item, - options: { - ...this.options, - ...item.options, - }, - }) - .wait() + // await effects + // .runRsync({ + // ...item, + // options: { + // ...this.options, + // ...item.options, + // }, + // }) + // .wait() } return } @@ -158,18 +158,18 @@ export class Backups { // }, // ) // } - await effects - .runRsync({ - options: { - ...this.options, - ...item.options, - }, - srcVolume: item.dstVolume, - dstVolume: item.srcVolume, - srcPath: item.dstPath, - dstPath: item.srcPath, - }) - .wait() + // await effects + // .runRsync({ + // options: { + // ...this.options, + // ...item.options, + // }, + // srcVolume: item.dstVolume, + // dstVolume: item.srcVolume, + // srcPath: item.dstPath, + // dstPath: item.srcPath, + // }) + // .wait() } return } diff --git a/lib/dependencyConfig/DependencyConfig.ts b/lib/dependencyConfig/DependencyConfig.ts index 42354a9..7d936bc 100644 --- a/lib/dependencyConfig/DependencyConfig.ts +++ b/lib/dependencyConfig/DependencyConfig.ts @@ -7,55 +7,39 @@ import { Utils, utils } from "../util/utils" import { deepEqual } from "../util/deepEqual" import { deepMerge } from "../util/deepMerge" +export type Update = (options: { + remoteConfig: RemoteConfig + queryResults: QueryResults +}) => Promise + export class DependencyConfig< Store, Input extends Record, RemoteConfig extends Record, > { + static defaultUpdate = async (options: { + queryResults: unknown + remoteConfig: unknown + }): Promise => { + return deepMerge({}, options.remoteConfig, options.queryResults || {}) + } constructor( readonly dependencyConfig: (options: { effects: Effects localConfig: Input - remoteConfig: RemoteConfig utils: Utils }) => Promise>, + readonly update: Update< + void | DeepPartial, + RemoteConfig + > = DependencyConfig.defaultUpdate as any, ) {} - async check( - options: Parameters[0], - ): ReturnType { - const origConfig = JSON.parse(JSON.stringify(options.localConfig)) - const newOptions = { - ...options, - utils: utils(options.effects), + async query(options: { effects: Effects; localConfig: unknown }) { + return this.dependencyConfig({ localConfig: options.localConfig as Input, - remoteConfig: options.remoteConfig as RemoteConfig, - } - if ( - !deepEqual( - origConfig, - deepMerge( - {}, - options.localConfig, - await this.dependencyConfig(newOptions), - ), - ) - ) - throw new Error(`Check failed`) - } - async autoConfigure( - options: Parameters[0], - ): ReturnType { - const newOptions = { - ...options, + effects: options.effects, utils: utils(options.effects), - localConfig: options.localConfig as Input, - remoteConfig: options.remoteConfig as any, - } - return deepMerge( - {}, - options.remoteConfig, - await this.dependencyConfig(newOptions), - ) + }) } } diff --git a/lib/health/checkFns/checkPortListening.ts b/lib/health/checkFns/checkPortListening.ts index fc98d29..c4772cf 100644 --- a/lib/health/checkFns/checkPortListening.ts +++ b/lib/health/checkFns/checkPortListening.ts @@ -1,4 +1,5 @@ import { Effects } from "../../types" +import { createUtils } from "../../util" import { CheckResult } from "./CheckResult" export function containsAddress(x: string, port: number) { const readPorts = x @@ -26,11 +27,15 @@ export async function checkPortListening( timeout?: number }, ): Promise { + const utils = createUtils(effects) return Promise.race([ Promise.resolve().then(async () => { const hasAddress = - containsAddress(await effects.runCommand(`cat /proc/net/tcp`), port) || - containsAddress(await effects.runCommand("cat /proc/net/udp"), port) + containsAddress( + await utils.runCommand(`cat /proc/net/tcp`, {}), + port, + ) || + containsAddress(await utils.runCommand("cat /proc/net/udp", {}), port) if (hasAddress) { return { status: "passing", message: options.successMessage } } diff --git a/lib/health/checkFns/checkWebUrl.ts b/lib/health/checkFns/checkWebUrl.ts index c0968be..1c09187 100644 --- a/lib/health/checkFns/checkWebUrl.ts +++ b/lib/health/checkFns/checkWebUrl.ts @@ -1,6 +1,7 @@ import { Effects } from "../../types" import { CheckResult } from "./CheckResult" import { timeoutPromise } from "./index" +import fetch from "node-fetch" /** * This is a helper function to check if a web url is reachable. @@ -17,7 +18,7 @@ export const checkWebUrl = async ( errorMessage = `Error while fetching URL: ${url}`, } = {}, ): Promise => { - return Promise.race([effects.fetch(url), timeoutPromise(timeout)]) + return Promise.race([fetch(url), timeoutPromise(timeout)]) .then((x) => ({ status: "passing" as const, message: successMessage, diff --git a/lib/health/checkFns/runHealthScript.ts b/lib/health/checkFns/runHealthScript.ts index b2071a3..bafa549 100644 --- a/lib/health/checkFns/runHealthScript.ts +++ b/lib/health/checkFns/runHealthScript.ts @@ -1,4 +1,5 @@ import { CommandType, Effects } from "../../types" +import { createUtils } from "../../util" import { CheckResult } from "./CheckResult" import { timeoutPromise } from "./index" @@ -9,9 +10,9 @@ import { timeoutPromise } from "./index" * @param param0 * @returns */ -export const runHealthScript = async ( +export const runHealthScript = async ( effects: Effects, - runCommand: CommandType, + runCommand: string, { timeout = 30000, errorMessage = `Error while running command: ${runCommand}`, @@ -19,8 +20,9 @@ export const runHealthScript = async ( `Have ran script ${runCommand} and the result: ${res}`, } = {}, ): Promise => { + const utils = createUtils(effects) const res = await Promise.race([ - effects.runCommand(runCommand, { timeoutMillis: timeout }), + utils.runCommand(runCommand, { timeout }), timeoutPromise(timeout), ]).catch((e) => { console.warn(errorMessage) diff --git a/lib/mainFn/Daemons.ts b/lib/mainFn/Daemons.ts index b89ab30..0dfa95d 100644 --- a/lib/mainFn/Daemons.ts +++ b/lib/mainFn/Daemons.ts @@ -4,9 +4,10 @@ import { Trigger } from "../trigger" import { TriggerInput } from "../trigger/TriggerInput" import { defaultTrigger } from "../trigger/defaultTrigger" import { DaemonReturned, Effects, ValidIfNoStupidEscape } from "../types" +import { createUtils } from "../util" type Daemon = { id: "" extends Id ? never : Id - command: ValidIfNoStupidEscape | [string, ...string[]] + command: string env?: Record ready: { display: string | null @@ -95,8 +96,9 @@ export class Daemons { ) daemonsStarted[daemon.id] = requiredPromise.then(async () => { const { command } = daemon + const utils = createUtils(effects) - const child = effects.runDaemon(command, { env: daemon.env }) + const child = utils.runDaemon(command, { env: daemon.env }) let currentInput: TriggerInput = {} const getCurrentInput = () => currentInput const trigger = (daemon.ready.trigger ?? defaultTrigger)( diff --git a/lib/types.ts b/lib/types.ts index a074b9f..285f7cb 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -97,16 +97,11 @@ export type VersionString = string * this is used to make sure that other dependencies have the values that this service could use. */ export type DependencyConfig = { - /** Checks are called to make sure that our dependency is in the correct shape. If a known error is returned we know that the dependency needs modification */ - check(options: { - effects: Effects - localConfig: unknown - remoteConfig: unknown - }): Promise - /** This is called after we know that the dependency package needs a new configuration, this would be a transform for defaults */ - autoConfigure(options: { - effects: Effects - localConfig: unknown + /** During autoconfigure, we have access to effects and local data. We are going to figure out all the data that we need and send it to update. For the sdk it is the desired delta */ + query(options: { effects: Effects; localConfig: unknown }): Promise + /** This is the second part. Given the query results off the previous function, we will determine what to change the remote config to. In our sdk normall we are going to use the previous as a deep merge. */ + update(options: { + queryResults: unknown remoteConfig: unknown }): Promise } diff --git a/lib/util/utils.ts b/lib/util/utils.ts index b238fab..4890063 100644 --- a/lib/util/utils.ts +++ b/lib/util/utils.ts @@ -6,6 +6,7 @@ import { checkWebUrl, } from "../health/checkFns" import { + DaemonReturned, Effects, EnsureStorePath, ExtractStore, @@ -34,6 +35,8 @@ import { getNetworkInterfaces, } from "./getNetworkInterfaces" +import { exec } from "node:child_process" + export type Utils = { checkPortListening( port: number, @@ -91,6 +94,14 @@ export type Utils = { } nullIfEmpty: typeof nullIfEmpty readFile: (fileHelper: FileHelper) => ReturnType["read"]> + runDaemon: ( + command: string, + options: { env?: Record }, + ) => Promise + runCommand: ( + command: string, + options: { env?: Record; timeout?: number }, + ) => Promise store: { get: ( packageId: string, @@ -111,71 +122,113 @@ export type Utils = { } export const utils = ( effects: Effects, -): Utils => ({ - createInterface: (options: { - name: string - id: string - description: string - hasPrimary: boolean - disabled: boolean - ui: boolean - username: null | string - path: string - search: Record - }) => new NetworkInterfaceBuilder({ ...options, effects }), - getSystemSmtp: () => - new GetSystemSmtp(effects) as GetSystemSmtp & WrapperOverWrite, +): Utils => { + return { + createInterface: (options: { + name: string + id: string + description: string + hasPrimary: boolean + disabled: boolean + ui: boolean + username: null | string + path: string + search: Record + }) => new NetworkInterfaceBuilder({ ...options, effects }), + getSystemSmtp: () => + new GetSystemSmtp(effects) as GetSystemSmtp & WrapperOverWrite, - host: { - static: (id: string) => new StaticHost({ id, effects }), - single: (id: string) => new SingleHost({ id, effects }), - multi: (id: string) => new MultiHost({ id, effects }), - }, - readFile: (fileHelper: FileHelper) => fileHelper.read(effects), - writeFile: (fileHelper: FileHelper, data: A) => - fileHelper.write(data, effects), - nullIfEmpty, + host: { + static: (id: string) => new StaticHost({ id, effects }), + single: (id: string) => new SingleHost({ id, effects }), + multi: (id: string) => new MultiHost({ id, effects }), + }, + readFile: (fileHelper: FileHelper) => fileHelper.read(effects), + writeFile: (fileHelper: FileHelper, data: A) => + fileHelper.write(data, effects), + nullIfEmpty, - networkInterface: { - getOwn: (interfaceId: InterfaceId) => - getNetworkInterface(effects, { interfaceId }) as GetNetworkInterface & - WrapperOverWrite, - get: (opts: { interfaceId: InterfaceId; packageId: PackageId }) => - getNetworkInterface(effects, opts) as GetNetworkInterface & - WrapperOverWrite, - getAllOwn: () => - getNetworkInterfaces(effects, {}) as GetNetworkInterfaces & - WrapperOverWrite, - getAll: (opts: { packageId: PackageId }) => - getNetworkInterfaces(effects, opts) as GetNetworkInterfaces & - WrapperOverWrite, - }, - store: { - get: ( - packageId: string, - path: EnsureStorePath, - ) => - getStore(effects, path as any, { - packageId, - }) as any, - getOwn: (path: EnsureStorePath) => - getStore(effects, path as any) as any, - setOwn: ( - path: EnsureStorePath, - value: ExtractStore, - ) => effects.store.set({ value, path: path as any }), - }, - checkPortListening: checkPortListening.bind(null, effects), - checkWebUrl: checkWebUrl.bind(null, effects), + networkInterface: { + getOwn: (interfaceId: InterfaceId) => + getNetworkInterface(effects, { interfaceId }) as GetNetworkInterface & + WrapperOverWrite, + get: (opts: { interfaceId: InterfaceId; packageId: PackageId }) => + getNetworkInterface(effects, opts) as GetNetworkInterface & + WrapperOverWrite, + getAllOwn: () => + getNetworkInterfaces(effects, {}) as GetNetworkInterfaces & + WrapperOverWrite, + getAll: (opts: { packageId: PackageId }) => + getNetworkInterfaces(effects, opts) as GetNetworkInterfaces & + WrapperOverWrite, + }, + store: { + get: ( + packageId: string, + path: EnsureStorePath, + ) => + getStore(effects, path as any, { + packageId, + }) as any, + getOwn: (path: EnsureStorePath) => + getStore(effects, path as any) as any, + setOwn: ( + path: EnsureStorePath, + value: ExtractStore, + ) => effects.store.set({ value, path: path as any }), + }, - mountDependencies: < - In extends - | Record>> - | Record> - | Record - | Path, - >( - value: In, - ) => mountDependencies(effects, value), -}) + runDaemon: async ( + command: string, + options: { env?: Record }, + ): Promise => { + let childProcess: null | { + kill(signal?: number | string): void + } = null + const answer = new Promise( + (resolve, reject) => + (childProcess = exec(command, options, (error, stdout, stderr) => { + if (error) return reject(error.toString()) + if (stderr) return reject(stderr.toString()) + return resolve(stdout.toString()) + })), + ) + + return { + wait() { + return answer + }, + async term() { + childProcess?.kill() + }, + } + }, + runCommand: async ( + command: string, + options: { env?: Record; timeout?: number }, + ): Promise => { + const answer = new Promise((resolve, reject) => + exec(command, options, (error, stdout, stderr) => { + if (error) return reject(error.toString()) + if (stderr) return reject(stderr.toString()) + return resolve(stdout.toString()) + }), + ) + + return answer + }, + checkPortListening: checkPortListening.bind(null, effects), + checkWebUrl: checkWebUrl.bind(null, effects), + + mountDependencies: < + In extends + | Record>> + | Record> + | Record + | Path, + >( + value: In, + ) => mountDependencies(effects, value), + } +} function noop(): void {} diff --git a/package-lock.json b/package-lock.json index efc6b45..171e4ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", + "node-fetch": "^3.3.1", "ts-matches": "^5.4.1", "yaml": "^2.2.2" }, @@ -2205,6 +2206,14 @@ "node": ">= 8" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2471,6 +2480,28 @@ "bser": "2.1.1" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2496,6 +2527,17 @@ "node": ">=8" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3719,6 +3761,41 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", + "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5107,6 +5184,14 @@ "makeerror": "1.0.12" } }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "engines": { + "node": ">= 8" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 761818f..11c13ee 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "homepage": "https://github.com/Start9Labs/start-sdk#readme", "dependencies": { "@iarna/toml": "^2.2.5", + "node-fetch": "^3.3.1", "ts-matches": "^5.4.1", "yaml": "^2.2.2" },