From 75ec297be1b3e54c58efd085b26faa4666faf554 Mon Sep 17 00:00:00 2001 From: BluJ Date: Mon, 24 Apr 2023 14:07:13 -0600 Subject: [PATCH] chore: Update the backups --- lib/backup/Backups.ts | 172 ++++++++++++++++++++++++++++++++++ lib/backup/index.ts | 170 +-------------------------------- lib/backup/setupBackups.ts | 30 ++++++ lib/index.ts | 1 + lib/manifest/ManifestTypes.ts | 56 +++++++++++ lib/manifest/index.ts | 2 + lib/manifest/setupManifest.ts | 8 ++ 7 files changed, 271 insertions(+), 168 deletions(-) create mode 100644 lib/backup/Backups.ts create mode 100644 lib/backup/setupBackups.ts create mode 100644 lib/manifest/ManifestTypes.ts create mode 100644 lib/manifest/index.ts create mode 100644 lib/manifest/setupManifest.ts diff --git a/lib/backup/Backups.ts b/lib/backup/Backups.ts new file mode 100644 index 0000000..0dd9022 --- /dev/null +++ b/lib/backup/Backups.ts @@ -0,0 +1,172 @@ +import { GenericManifest } from "../manifest/ManifestTypes"; +import * as T from "../types"; + +export const DEFAULT_OPTIONS: T.BackupOptions = { + delete: true, + force: true, + ignoreExisting: false, + exclude: [], +}; +type BackupSet = { + srcPath: string; + srcVolume: string; + dstPath: string; + dstVolume: string; + options?: Partial; +}; +/** + * This utility simplifies the volume backup process. + * ```ts + * export const { createBackup, restoreBackup } = Backups.volumes("main").build(); + * ``` + * + * Changing the options of the rsync, (ie exludes) use either + * ```ts + * Backups.volumes("main").set_options({exclude: ['bigdata/']}).volumes('excludedVolume').build() + * // or + * Backups.with_options({exclude: ['bigdata/']}).volumes('excludedVolume').build() + * ``` + * + * Using the more fine control, using the addSets for more control + * ```ts + * Backups.addSets({ + * srcVolume: 'main', srcPath:'smallData/', dstPath: 'main/smallData/', dstVolume: : Backups.BACKUP + * }, { + * srcVolume: 'main', srcPath:'bigData/', dstPath: 'main/bigData/', dstVolume: : Backups.BACKUP, options: {exclude:['bigData/excludeThis']}} + * ).build()q + * ``` + */ +export class Backups { + static BACKUP = "BACKUP" as const; + + constructor( + private options = DEFAULT_OPTIONS, + private backupSet = [] as BackupSet[], + ) {} + static volumes( + ...volumeNames: Array + ) { + return new Backups().addSets( + ...volumeNames.map((srcVolume) => ({ + srcVolume, + srcPath: "./", + dstPath: `./${srcVolume}/`, + dstVolume: Backups.BACKUP, + })), + ); + } + static addSets(...options: BackupSet[]) { + return new Backups().addSets(...options); + } + static with_options(options?: Partial) { + return new Backups({ ...DEFAULT_OPTIONS, ...options }); + } + set_options(options?: Partial) { + this.options = { + ...this.options, + ...options, + }; + return this; + } + volumes(...volumeNames: Array) { + return this.addSets( + ...volumeNames.map((srcVolume) => ({ + srcVolume, + srcPath: "./", + dstPath: `./${srcVolume}/`, + dstVolume: Backups.BACKUP, + })), + ); + } + addSets(...options: BackupSet[]) { + options.forEach((x) => + this.backupSet.push({ ...x, options: { ...this.options, ...x.options } }), + ); + return this; + } + build() { + const createBackup: T.ExpectedExports.createBackup = async ({ + effects, + }) => { + const previousItems = ( + await effects + .readDir({ + volumeId: Backups.BACKUP, + path: ".", + }) + .catch(() => []) + ).map((x) => `${x}`); + const backupPaths = this.backupSet + .filter((x) => x.dstVolume === Backups.BACKUP) + .map((x) => x.dstPath) + .map((x) => x.replace(/\.\/([^]*)\//, "$1")); + const filteredItems = previousItems.filter( + (x) => backupPaths.indexOf(x) === -1, + ); + for (const itemToRemove of filteredItems) { + effects.error(`Trying to remove ${itemToRemove}`); + await effects + .removeDir({ + volumeId: Backups.BACKUP, + path: itemToRemove, + }) + .catch(() => + effects.removeFile({ + volumeId: Backups.BACKUP, + path: itemToRemove, + }), + ) + .catch(() => { + effects.warn(`Failed to remove ${itemToRemove} from backup volume`); + }); + } + for (const item of this.backupSet) { + if (notEmptyPath(item.dstPath)) { + await effects.createDir({ + volumeId: item.dstVolume, + path: item.dstPath, + }); + } + await effects + .runRsync({ + ...item, + options: { + ...this.options, + ...item.options, + }, + }) + .wait(); + } + return; + }; + const restoreBackup: T.ExpectedExports.restoreBackup = async ({ + effects, + }) => { + for (const item of this.backupSet) { + if (notEmptyPath(item.srcPath)) { + await effects.createDir({ + volumeId: item.srcVolume, + path: item.srcPath, + }); + } + await effects + .runRsync({ + options: { + ...this.options, + ...item.options, + }, + srcVolume: item.dstVolume, + dstVolume: item.srcVolume, + srcPath: item.dstPath, + dstPath: item.srcPath, + }) + .wait(); + } + return; + }; + return { createBackup, restoreBackup }; + } +} +function notEmptyPath(file: string) { + return ["", ".", "./"].indexOf(file) === -1; +} diff --git a/lib/backup/index.ts b/lib/backup/index.ts index e6a4026..a2f1d57 100644 --- a/lib/backup/index.ts +++ b/lib/backup/index.ts @@ -1,169 +1,3 @@ -import * as T from "../types"; +export { Backups } from "./Backups"; -export const DEFAULT_OPTIONS: T.BackupOptions = { - delete: true, - force: true, - ignoreExisting: false, - exclude: [], -}; -type BackupSet = { - srcPath: string; - srcVolume: string; - dstPath: string; - dstVolume: string; - options?: Partial; -}; -/** - * This utility simplifies the volume backup process. - * ```ts - * export const { createBackup, restoreBackup } = Backups.volumes("main").build(); - * ``` - * - * Changing the options of the rsync, (ie exludes) use either - * ```ts - * Backups.volumes("main").set_options({exclude: ['bigdata/']}).volumes('excludedVolume').build() - * // or - * Backups.with_options({exclude: ['bigdata/']}).volumes('excludedVolume').build() - * ``` - * - * Using the more fine control, using the addSets for more control - * ```ts - * Backups.addSets({ - * srcVolume: 'main', srcPath:'smallData/', dstPath: 'main/smallData/', dstVolume: : Backups.BACKUP - * }, { - * srcVolume: 'main', srcPath:'bigData/', dstPath: 'main/bigData/', dstVolume: : Backups.BACKUP, options: {exclude:['bigData/excludeThis']}} - * ).build()q - * ``` - */ -export class Backups { - static BACKUP = "BACKUP" as const; - - constructor( - private options = DEFAULT_OPTIONS, - private backupSet = [] as BackupSet[], - ) {} - static volumes(...volumeNames: string[]) { - return new Backups().addSets( - ...volumeNames.map((srcVolume) => ({ - srcVolume, - srcPath: "./", - dstPath: `./${srcVolume}/`, - dstVolume: Backups.BACKUP, - })), - ); - } - static addSets(...options: BackupSet[]) { - return new Backups().addSets(...options); - } - static with_options(options?: Partial) { - return new Backups({ ...DEFAULT_OPTIONS, ...options }); - } - set_options(options?: Partial) { - this.options = { - ...this.options, - ...options, - }; - return this; - } - volumes(...volumeNames: string[]) { - return this.addSets( - ...volumeNames.map((srcVolume) => ({ - srcVolume, - srcPath: "./", - dstPath: `./${srcVolume}/`, - dstVolume: Backups.BACKUP, - })), - ); - } - addSets(...options: BackupSet[]) { - options.forEach((x) => - this.backupSet.push({ ...x, options: { ...this.options, ...x.options } }), - ); - return this; - } - build() { - const createBackup: T.ExpectedExports.createBackup = async ({ - effects, - }) => { - const previousItems = ( - await effects - .readDir({ - volumeId: Backups.BACKUP, - path: ".", - }) - .catch(() => []) - ).map((x) => `${x}`); - const backupPaths = this.backupSet - .filter((x) => x.dstVolume === Backups.BACKUP) - .map((x) => x.dstPath) - .map((x) => x.replace(/\.\/([^]*)\//, "$1")); - const filteredItems = previousItems.filter( - (x) => backupPaths.indexOf(x) === -1, - ); - for (const itemToRemove of filteredItems) { - effects.error(`Trying to remove ${itemToRemove}`); - await effects - .removeDir({ - volumeId: Backups.BACKUP, - path: itemToRemove, - }) - .catch(() => - effects.removeFile({ - volumeId: Backups.BACKUP, - path: itemToRemove, - }), - ) - .catch(() => { - effects.warn(`Failed to remove ${itemToRemove} from backup volume`); - }); - } - for (const item of this.backupSet) { - if (notEmptyPath(item.dstPath)) { - await effects.createDir({ - volumeId: item.dstVolume, - path: item.dstPath, - }); - } - await effects - .runRsync({ - ...item, - options: { - ...this.options, - ...item.options, - }, - }) - .wait(); - } - return; - }; - const restoreBackup: T.ExpectedExports.restoreBackup = async ({ - effects, - }) => { - for (const item of this.backupSet) { - if (notEmptyPath(item.srcPath)) { - await effects.createDir({ - volumeId: item.srcVolume, - path: item.srcPath, - }); - } - await effects - .runRsync({ - options: { - ...this.options, - ...item.options, - }, - srcVolume: item.dstVolume, - dstVolume: item.srcVolume, - srcPath: item.dstPath, - dstPath: item.srcPath, - }) - .wait(); - } - return; - }; - return { createBackup, restoreBackup }; - } -} -function notEmptyPath(file: string) { - return ["", ".", "./"].indexOf(file) === -1; -} +export { setupBackups } from "./setupBackups"; diff --git a/lib/backup/setupBackups.ts b/lib/backup/setupBackups.ts new file mode 100644 index 0000000..ed2f4f0 --- /dev/null +++ b/lib/backup/setupBackups.ts @@ -0,0 +1,30 @@ +import { string } from "ts-matches"; +import { Backups } from "."; +import { GenericManifest } from "../manifest/ManifestTypes"; +import { BackupOptions } from "../types"; + +export type SetupBackupsParams = + | [Partial, ...Array] + | Array; + +export function setupBackups( + ...args: SetupBackupsParams +) { + const [options, volumes] = splitOptions(args); + if (!options) { + return Backups.volumes(...volumes).build(); + } + return Backups.with_options(options) + .volumes(...volumes) + .build(); +} + +function splitOptions( + args: SetupBackupsParams, +): [null | Partial, Array] { + if (args.length > 0 && !string.test(args[0])) { + const [options, ...restVolumes] = args; + return [options, restVolumes as Array]; + } + return [null, args as Array]; +} diff --git a/lib/index.ts b/lib/index.ts index a330113..0c9fcdf 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -14,3 +14,4 @@ export * as YAML from "yaml"; export * as properties from "./properties"; export * as autoconfig from "./autoconfig"; export * as actions from "./actions"; +export * as manifest from "./manifest"; diff --git a/lib/manifest/ManifestTypes.ts b/lib/manifest/ManifestTypes.ts new file mode 100644 index 0000000..9137881 --- /dev/null +++ b/lib/manifest/ManifestTypes.ts @@ -0,0 +1,56 @@ +export interface Container { + image: string; + mounts: Record; + shmSizeMb?: number; // if greater + sigtermTimeout?: string; // if more than 30s to shutdown +} + +export interface GenericManifest { + id: string; + title: string; + version: string; + releaseNotes: string; + license: string; // name of license + replaces: string[]; + wrapperRepo: string; + upstreamRepo: string; + supportSite: string; + marketingSite: string; + donationUrl: string | null; + description: { + short: string; + long: string; + }; + assets: { + icon: string; // file path + instructions: string; // file path + license: string; // file path + }; + containers: Record; + volumes: Record; + alerts: { + install: string | null; + uninstall: string | null; + restore: string | null; + start: string | null; + stop: string | null; + }; + dependencies: Record; +} + +export interface Dependency { + version: string; + description: string | null; + requirement: + | { + type: "opt-in"; + how: string; + } + | { + type: "opt-out"; + how: string; + } + | { + type: "required"; + }; +} diff --git a/lib/manifest/index.ts b/lib/manifest/index.ts new file mode 100644 index 0000000..01aeefd --- /dev/null +++ b/lib/manifest/index.ts @@ -0,0 +1,2 @@ +export { setupManifest } from "./setupManifest"; +export * as ManifestTypes from "./ManifestTypes"; diff --git a/lib/manifest/setupManifest.ts b/lib/manifest/setupManifest.ts new file mode 100644 index 0000000..f301872 --- /dev/null +++ b/lib/manifest/setupManifest.ts @@ -0,0 +1,8 @@ +import { GenericManifest } from "./ManifestTypes"; + +export function setupManifest< + M extends GenericManifest & { id: Id }, + Id extends string, +>(manifest: M): M { + return manifest; +}