chore: remove Flow
It was not used nor maintained by XO devs, and was causing issues with editors. JSDoc or TypeScript should be used instead.
This commit is contained in:
@@ -45,7 +45,6 @@
|
||||
"@babel/cli": "^7.0.0",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/preset-flow": "^7.0.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"rimraf": "^3.0.0"
|
||||
},
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
"@babel/plugin-proposal-function-bind": "^7.0.0",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/preset-flow": "^7.0.0",
|
||||
"async-iterator-to-stream": "^1.1.0",
|
||||
"babel-plugin-lodash": "^3.3.2",
|
||||
"cross-env": "^7.0.2",
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
// @flow
|
||||
|
||||
// $FlowFixMe
|
||||
import getStream from 'get-stream'
|
||||
|
||||
import asyncMapSettled from '@xen-orchestra/async-map/legacy'
|
||||
import getStream from 'get-stream'
|
||||
import limit from 'limit-concurrency-decorator'
|
||||
import path, { basename } from 'path'
|
||||
import synchronized from 'decorator-synchronized'
|
||||
@@ -11,24 +7,14 @@ import { coalesceCalls } from '@vates/coalesce-calls'
|
||||
import { fromCallback, fromEvent, ignoreErrors, timeout } from 'promise-toolbox'
|
||||
import { parse } from 'xo-remote-parser'
|
||||
import { randomBytes } from 'crypto'
|
||||
import { type Readable, type Writable } from 'stream'
|
||||
|
||||
import normalizePath from './_normalizePath'
|
||||
import { createChecksumStream, validChecksumOfReadStream } from './checksum'
|
||||
|
||||
const { dirname } = path.posix
|
||||
|
||||
type Data = Buffer | Readable | string
|
||||
type Disposable<T> = {| dispose: () => void | Promise<void>, value?: T |}
|
||||
type FileDescriptor = {| fd: mixed, path: string |}
|
||||
type LaxReadable = Readable & Object
|
||||
type LaxWritable = Writable & Object
|
||||
type RemoteInfo = { used?: number, size?: number }
|
||||
|
||||
type File = FileDescriptor | string
|
||||
|
||||
const checksumFile = file => file + '.checksum'
|
||||
const computeRate = (hrtime: number[], size: number) => {
|
||||
const computeRate = (hrtime, size) => {
|
||||
const seconds = hrtime[0] + hrtime[1] / 1e9
|
||||
return size / seconds
|
||||
}
|
||||
@@ -74,11 +60,7 @@ class PrefixWrapper {
|
||||
}
|
||||
|
||||
export default class RemoteHandlerAbstract {
|
||||
_highWaterMark: number
|
||||
_remote: Object
|
||||
_timeout: number
|
||||
|
||||
constructor(remote: any, options: Object = {}) {
|
||||
constructor(remote, options = {}) {
|
||||
if (remote.url === 'test://') {
|
||||
this._remote = remote
|
||||
} else {
|
||||
@@ -112,21 +94,21 @@ export default class RemoteHandlerAbstract {
|
||||
|
||||
// Public members
|
||||
|
||||
get type(): string {
|
||||
get type() {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
addPrefix(prefix: string) {
|
||||
addPrefix(prefix) {
|
||||
prefix = normalizePath(prefix)
|
||||
return prefix === '/' ? this : new PrefixWrapper(this, prefix)
|
||||
}
|
||||
|
||||
async closeFile(fd: FileDescriptor): Promise<void> {
|
||||
async closeFile(fd) {
|
||||
await this.__closeFile(fd)
|
||||
}
|
||||
|
||||
// TODO: remove method
|
||||
async createOutputStream(file: File, { checksum = false, dirMode, ...options }: Object = {}): Promise<LaxWritable> {
|
||||
async createOutputStream(file, { checksum = false, dirMode, ...options } = {}) {
|
||||
if (typeof file === 'string') {
|
||||
file = normalizePath(file)
|
||||
}
|
||||
@@ -153,7 +135,6 @@ export default class RemoteHandlerAbstract {
|
||||
stream.on('error', forwardError)
|
||||
checksumStream.pipe(stream)
|
||||
|
||||
// $FlowFixMe
|
||||
checksumStream.checksumWritten = checksumStream.checksum
|
||||
.then(value => this._outputFile(checksumFile(path), value, { flags: 'wx' }))
|
||||
.catch(forwardError)
|
||||
@@ -161,10 +142,7 @@ export default class RemoteHandlerAbstract {
|
||||
return checksumStream
|
||||
}
|
||||
|
||||
createReadStream(
|
||||
file: File,
|
||||
{ checksum = false, ignoreMissingChecksum = false, ...options }: Object = {}
|
||||
): Promise<LaxReadable> {
|
||||
createReadStream(file, { checksum = false, ignoreMissingChecksum = false, ...options } = {}) {
|
||||
if (typeof file === 'string') {
|
||||
file = normalizePath(file)
|
||||
}
|
||||
@@ -201,7 +179,7 @@ export default class RemoteHandlerAbstract {
|
||||
checksum =>
|
||||
streamP.then(stream => {
|
||||
const { length } = stream
|
||||
stream = (validChecksumOfReadStream(stream, String(checksum).trim()): LaxReadable)
|
||||
stream = validChecksumOfReadStream(stream, String(checksum).trim())
|
||||
stream.length = length
|
||||
|
||||
return stream
|
||||
@@ -216,11 +194,7 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
|
||||
// write a stream to a file using a temporary file
|
||||
async outputStream(
|
||||
path: string,
|
||||
input: Readable | Promise<Readable>,
|
||||
{ checksum = true, dirMode }: { checksum?: boolean, dirMode?: number } = {}
|
||||
): Promise<void> {
|
||||
async outputStream(path, input, { checksum = true, dirMode } = {}) {
|
||||
return this._outputStream(normalizePath(path), await input, {
|
||||
checksum,
|
||||
dirMode,
|
||||
@@ -234,22 +208,19 @@ export default class RemoteHandlerAbstract {
|
||||
// as mount), forgetting them might breaking other processes using the same
|
||||
// remote.
|
||||
@synchronized()
|
||||
async forget(): Promise<void> {
|
||||
async forget() {
|
||||
await this._forget()
|
||||
}
|
||||
|
||||
async getInfo(): Promise<RemoteInfo> {
|
||||
async getInfo() {
|
||||
return timeout.call(this._getInfo(), this._timeout)
|
||||
}
|
||||
|
||||
async getSize(file: File): Promise<number> {
|
||||
async getSize(file) {
|
||||
return timeout.call(this._getSize(typeof file === 'string' ? normalizePath(file) : file), this._timeout)
|
||||
}
|
||||
|
||||
async list(
|
||||
dir: string,
|
||||
{ filter, prependDir = false }: { filter?: (name: string) => boolean, prependDir?: boolean } = {}
|
||||
): Promise<string[]> {
|
||||
async list(dir, { filter, prependDir = false } = {}) {
|
||||
const virtualDir = normalizePath(dir)
|
||||
dir = normalizePath(dir)
|
||||
|
||||
@@ -267,40 +238,36 @@ export default class RemoteHandlerAbstract {
|
||||
return entries
|
||||
}
|
||||
|
||||
async lock(path: string): Promise<Disposable> {
|
||||
async lock(path) {
|
||||
path = normalizePath(path)
|
||||
return { dispose: await this._lock(path) }
|
||||
}
|
||||
|
||||
async mkdir(dir: string, { mode }: { mode?: number } = {}): Promise<void> {
|
||||
async mkdir(dir, { mode } = {}) {
|
||||
await this.__mkdir(normalizePath(dir), { mode })
|
||||
}
|
||||
|
||||
async mktree(dir: string, { mode }: { mode?: number } = {}): Promise<void> {
|
||||
async mktree(dir, { mode } = {}) {
|
||||
await this._mktree(normalizePath(dir), { mode })
|
||||
}
|
||||
|
||||
openFile(path: string, flags: string): Promise<FileDescriptor> {
|
||||
openFile(path, flags) {
|
||||
return this.__openFile(path, flags)
|
||||
}
|
||||
|
||||
async outputFile(
|
||||
file: string,
|
||||
data: Data,
|
||||
{ dirMode, flags = 'wx' }: { dirMode?: number, flags?: string } = {}
|
||||
): Promise<void> {
|
||||
async outputFile(file, data, { dirMode, flags = 'wx' } = {}) {
|
||||
await this._outputFile(normalizePath(file), data, { dirMode, flags })
|
||||
}
|
||||
|
||||
async read(file: File, buffer: Buffer, position?: number): Promise<{| bytesRead: number, buffer: Buffer |}> {
|
||||
async read(file, buffer, position) {
|
||||
return this._read(typeof file === 'string' ? normalizePath(file) : file, buffer, position)
|
||||
}
|
||||
|
||||
async readFile(file: string, { flags = 'r' }: { flags?: string } = {}): Promise<Buffer> {
|
||||
async readFile(file, { flags = 'r' } = {}) {
|
||||
return this._readFile(normalizePath(file), { flags })
|
||||
}
|
||||
|
||||
async rename(oldPath: string, newPath: string, { checksum = false }: Object = {}) {
|
||||
async rename(oldPath, newPath, { checksum = false } = {}) {
|
||||
oldPath = normalizePath(oldPath)
|
||||
newPath = normalizePath(newPath)
|
||||
|
||||
@@ -311,11 +278,11 @@ export default class RemoteHandlerAbstract {
|
||||
return p
|
||||
}
|
||||
|
||||
async rmdir(dir: string): Promise<void> {
|
||||
async rmdir(dir) {
|
||||
await timeout.call(this._rmdir(normalizePath(dir)).catch(ignoreEnoent), this._timeout)
|
||||
}
|
||||
|
||||
async rmtree(dir: string): Promise<void> {
|
||||
async rmtree(dir) {
|
||||
await this._rmtree(normalizePath(dir))
|
||||
}
|
||||
|
||||
@@ -324,11 +291,11 @@ export default class RemoteHandlerAbstract {
|
||||
//
|
||||
// This method MUST ALWAYS be called before using the handler.
|
||||
@synchronized()
|
||||
async sync(): Promise<void> {
|
||||
async sync() {
|
||||
await this._sync()
|
||||
}
|
||||
|
||||
async test(): Promise<Object> {
|
||||
async test() {
|
||||
const SIZE = 1024 * 1024 * 10
|
||||
const testFileName = normalizePath(`${Date.now()}.test`)
|
||||
const data = await fromCallback(randomBytes, SIZE)
|
||||
@@ -363,11 +330,11 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
async truncate(file: string, len: number): Promise<void> {
|
||||
async truncate(file, len) {
|
||||
await this._truncate(file, len)
|
||||
}
|
||||
|
||||
async unlink(file: string, { checksum = true }: Object = {}): Promise<void> {
|
||||
async unlink(file, { checksum = true } = {}) {
|
||||
file = normalizePath(file)
|
||||
|
||||
if (checksum) {
|
||||
@@ -377,21 +344,21 @@ export default class RemoteHandlerAbstract {
|
||||
await this._unlink(file).catch(ignoreEnoent)
|
||||
}
|
||||
|
||||
async write(file: File, buffer: Buffer, position: number): Promise<{| bytesWritten: number, buffer: Buffer |}> {
|
||||
async write(file, buffer, position) {
|
||||
await this._write(typeof file === 'string' ? normalizePath(file) : file, buffer, position)
|
||||
}
|
||||
|
||||
async writeFile(file: string, data: Data, { flags = 'wx' }: { flags?: string } = {}): Promise<void> {
|
||||
async writeFile(file, data, { flags = 'wx' } = {}) {
|
||||
await this._writeFile(normalizePath(file), data, { flags })
|
||||
}
|
||||
|
||||
// Methods that can be called by private methods to avoid parallel limit on public methods
|
||||
|
||||
async __closeFile(fd: FileDescriptor): Promise<void> {
|
||||
async __closeFile(fd) {
|
||||
await timeout.call(this._closeFile(fd.fd), this._timeout)
|
||||
}
|
||||
|
||||
async __mkdir(dir: string, { mode }: { mode?: number } = {}): Promise<void> {
|
||||
async __mkdir(dir, { mode } = {}) {
|
||||
try {
|
||||
await this._mkdir(dir, { mode })
|
||||
} catch (error) {
|
||||
@@ -404,7 +371,7 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
async __openFile(path: string, flags: string): Promise<FileDescriptor> {
|
||||
async __openFile(path, flags) {
|
||||
path = normalizePath(path)
|
||||
|
||||
return {
|
||||
@@ -415,11 +382,11 @@ export default class RemoteHandlerAbstract {
|
||||
|
||||
// Methods that can be implemented by inheriting classes
|
||||
|
||||
async _closeFile(fd: mixed): Promise<void> {
|
||||
async _closeFile(fd) {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
async _createOutputStream(file: File, { dirMode, ...options }: Object = {}): Promise<LaxWritable> {
|
||||
async _createOutputStream(file, { dirMode, ...options } = {}) {
|
||||
try {
|
||||
return await this._createWriteStream(file, { ...options, highWaterMark: this._highWaterMark })
|
||||
} catch (error) {
|
||||
@@ -432,40 +399,40 @@ export default class RemoteHandlerAbstract {
|
||||
return this._createOutputStream(file, options)
|
||||
}
|
||||
|
||||
async _createReadStream(file: File, options?: Object): Promise<LaxReadable> {
|
||||
async _createReadStream(file, options) {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
// createWriteStream takes highWaterMark as option even if it's not documented.
|
||||
// Source: https://stackoverflow.com/questions/55026306/how-to-set-writeable-highwatermark
|
||||
async _createWriteStream(file: File, options: Object): Promise<LaxWritable> {
|
||||
async _createWriteStream(file, options) {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
// called to finalize the remote
|
||||
async _forget(): Promise<void> {}
|
||||
async _forget() {}
|
||||
|
||||
async _getInfo(): Promise<Object> {
|
||||
async _getInfo() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async _lock(path: string): Promise<Function> {
|
||||
async _lock(path) {
|
||||
return () => Promise.resolve()
|
||||
}
|
||||
|
||||
async _getSize(file: File): Promise<number> {
|
||||
async _getSize(file) {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
async _list(dir: string): Promise<string[]> {
|
||||
async _list(dir) {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
async _mkdir(dir: string): Promise<void> {
|
||||
async _mkdir(dir) {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
async _mktree(dir: string, { mode }: { mode?: number } = {}): Promise<void> {
|
||||
async _mktree(dir, { mode } = {}) {
|
||||
try {
|
||||
return await this.__mkdir(dir, { mode })
|
||||
} catch (error) {
|
||||
@@ -478,11 +445,11 @@ export default class RemoteHandlerAbstract {
|
||||
return this._mktree(dir, { mode })
|
||||
}
|
||||
|
||||
async _openFile(path: string, flags: string): Promise<mixed> {
|
||||
async _openFile(path, flags) {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
async _outputFile(file: string, data: Data, { dirMode, flags }: { dirMode?: number, flags?: string }): Promise<void> {
|
||||
async _outputFile(file, data, { dirMode, flags }) {
|
||||
try {
|
||||
return await this._writeFile(file, data, { flags })
|
||||
} catch (error) {
|
||||
@@ -495,7 +462,7 @@ export default class RemoteHandlerAbstract {
|
||||
return this._outputFile(file, data, { flags })
|
||||
}
|
||||
|
||||
async _outputStream(path: string, input: Readable, { checksum, dirMode }: { checksum?: boolean, dirMode?: number }) {
|
||||
async _outputStream(path, input, { checksum, dirMode }) {
|
||||
const tmpPath = `${dirname(path)}/.${basename(path)}`
|
||||
const output = await this.createOutputStream(tmpPath, {
|
||||
checksum,
|
||||
@@ -505,7 +472,6 @@ export default class RemoteHandlerAbstract {
|
||||
input.pipe(output)
|
||||
await fromEvent(output, 'finish')
|
||||
await output.checksumWritten
|
||||
// $FlowFixMe
|
||||
await input.task
|
||||
await this.rename(tmpPath, path, { checksum })
|
||||
} catch (error) {
|
||||
@@ -514,23 +480,23 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
_read(file: File, buffer: Buffer, position?: number): Promise<{| bytesRead: number, buffer: Buffer |}> {
|
||||
_read(file, buffer, position) {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
_readFile(file: string, options?: Object): Promise<Buffer> {
|
||||
_readFile(file, options) {
|
||||
return this._createReadStream(file, { ...options, highWaterMark: this._highWaterMark }).then(getStream.buffer)
|
||||
}
|
||||
|
||||
async _rename(oldPath: string, newPath: string) {
|
||||
async _rename(oldPath, newPath) {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
async _rmdir(dir: string) {
|
||||
async _rmdir(dir) {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
async _rmtree(dir: string) {
|
||||
async _rmtree(dir) {
|
||||
try {
|
||||
return await this._rmdir(dir)
|
||||
} catch (error) {
|
||||
@@ -552,13 +518,13 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
|
||||
// called to initialize the remote
|
||||
async _sync(): Promise<void> {}
|
||||
async _sync() {}
|
||||
|
||||
async _unlink(file: string): Promise<void> {
|
||||
async _unlink(file) {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
async _write(file: File, buffer: Buffer, position: number): Promise<void> {
|
||||
async _write(file, buffer, position) {
|
||||
const isPath = typeof file === 'string'
|
||||
if (isPath) {
|
||||
file = await this.__openFile(file, 'r+')
|
||||
@@ -572,11 +538,11 @@ export default class RemoteHandlerAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
async _writeFd(fd: FileDescriptor, buffer: Buffer, position: number): Promise<void> {
|
||||
async _writeFd(fd, buffer, position) {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
async _writeFile(file: string, data: Data, options: { flags?: string }): Promise<void> {
|
||||
async _writeFile(file, data, options) {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import through2 from 'through2'
|
||||
import { createHash } from 'crypto'
|
||||
import { defer, fromEvent } from 'promise-toolbox'
|
||||
import { invert } from 'lodash'
|
||||
import { type Readable, type Transform } from 'stream'
|
||||
|
||||
// Format: $<algorithm>$<salt>$<encrypted>
|
||||
//
|
||||
@@ -27,7 +24,7 @@ const ID_TO_ALGORITHM = invert(ALGORITHM_TO_ID)
|
||||
// const checksumStream = source.pipe(createChecksumStream())
|
||||
// checksumStream.resume() // make the data flow without an output
|
||||
// console.log(await checksumStream.checksum)
|
||||
export const createChecksumStream = (algorithm: string = 'md5'): Transform & { checksum: Promise<string> } => {
|
||||
export const createChecksumStream = (algorithm = 'md5') => {
|
||||
const algorithmId = ALGORITHM_TO_ID[algorithm]
|
||||
|
||||
if (!algorithmId) {
|
||||
@@ -54,10 +51,7 @@ export const createChecksumStream = (algorithm: string = 'md5'): Transform & { c
|
||||
// Check if the checksum of a readable stream is equals to an expected checksum.
|
||||
// The given stream is wrapped in a stream which emits an error event
|
||||
// if the computed checksum is not equals to the expected checksum.
|
||||
export const validChecksumOfReadStream = (
|
||||
stream: Readable,
|
||||
expectedChecksum: string
|
||||
): Readable & { checksumVerified: Promise<void> } => {
|
||||
export const validChecksumOfReadStream = (stream, expectedChecksum) => {
|
||||
const algorithmId = expectedChecksum.slice(1, expectedChecksum.indexOf('$', 1))
|
||||
|
||||
if (!algorithmId) {
|
||||
@@ -66,7 +60,7 @@ export const validChecksumOfReadStream = (
|
||||
|
||||
const hash = createHash(ID_TO_ALGORITHM[algorithmId])
|
||||
|
||||
const wrapper: any = stream.pipe(
|
||||
const wrapper = stream.pipe(
|
||||
through2(
|
||||
{ highWaterMark: 0 },
|
||||
(chunk, enc, callback) => {
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
// @flow
|
||||
import execa from 'execa'
|
||||
|
||||
import type RemoteHandler from './abstract'
|
||||
import RemoteHandlerLocal from './local'
|
||||
import RemoteHandlerNfs from './nfs'
|
||||
import RemoteHandlerS3 from './s3'
|
||||
import RemoteHandlerSmb from './smb'
|
||||
import RemoteHandlerSmbMount from './smb-mount'
|
||||
|
||||
export type { default as RemoteHandler } from './abstract'
|
||||
export type Remote = { url: string }
|
||||
|
||||
const HANDLERS = {
|
||||
file: RemoteHandlerLocal,
|
||||
nfs: RemoteHandlerNfs,
|
||||
@@ -24,7 +19,7 @@ try {
|
||||
HANDLERS.smb = RemoteHandlerSmb
|
||||
}
|
||||
|
||||
export const getHandler = (remote: Remote, ...rest: any): RemoteHandler => {
|
||||
export const getHandler = (remote, ...rest) => {
|
||||
// FIXME: should be done in xo-remote-parser.
|
||||
const type = remote.url.split('://')[0]
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@
|
||||
"@babel/cli": "^7.0.0",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/preset-flow": "^7.0.0",
|
||||
"babel-plugin-lodash": "^3.3.2",
|
||||
"cross-env": "^7.0.2",
|
||||
"rimraf": "^3.0.0"
|
||||
|
||||
4
flow-typed/limit-concurrency-decorator.js
vendored
4
flow-typed/limit-concurrency-decorator.js
vendored
@@ -1,4 +0,0 @@
|
||||
declare module 'limit-concurrency-decorator' {
|
||||
declare function limitConcurrencyDecorator(concurrency: number): <T: Function>(T) => T
|
||||
declare export default typeof limitConcurrencyDecorator
|
||||
}
|
||||
15
flow-typed/lodash.js
vendored
15
flow-typed/lodash.js
vendored
@@ -1,15 +0,0 @@
|
||||
declare module 'lodash' {
|
||||
declare export function countBy<K, V>(object: { [K]: V }, iteratee: K | ((V, K) => string)): { [string]: number }
|
||||
declare export function forEach<K, V>(object: { [K]: V }, iteratee: (V, K) => void): void
|
||||
declare export function groupBy<K, V>(object: { [K]: V }, iteratee: K | ((V, K) => string)): { [string]: V[] }
|
||||
declare export function invert<K, V>(object: { [K]: V }): { [V]: K }
|
||||
declare export function isEmpty(mixed): boolean
|
||||
declare export function keyBy<T>(array: T[], iteratee: string): boolean
|
||||
declare export function last<T>(array?: T[]): T | void
|
||||
declare export function map<T1, T2>(collection: T1[], iteratee: (T1) => T2): T2[]
|
||||
declare export function mapValues<K, V1, V2>(object: { [K]: V1 }, iteratee: (V1, K) => V2): { [K]: V2 }
|
||||
declare export function noop(...args: mixed[]): void
|
||||
declare export function some<T>(collection: T[], iteratee: (T, number) => boolean): boolean
|
||||
declare export function sum(values: number[]): number
|
||||
declare export function values<K, V>(object: { [K]: V }): V[]
|
||||
}
|
||||
15
flow-typed/promise-toolbox.js
vendored
15
flow-typed/promise-toolbox.js
vendored
@@ -1,15 +0,0 @@
|
||||
declare module 'promise-toolbox' {
|
||||
declare export class CancelToken {
|
||||
static source(): { cancel: (message: any) => void, token: CancelToken };
|
||||
}
|
||||
declare export function cancelable(Function): Function
|
||||
declare export function defer<T>(): {|
|
||||
promise: Promise<T>,
|
||||
reject: T => void,
|
||||
resolve: T => void,
|
||||
|}
|
||||
declare export function fromCallback<T>((cb: (error: any, value: T) => void) => void): Promise<T>
|
||||
declare export function fromEvent(emitter: mixed, string): Promise<mixed>
|
||||
declare export function ignoreErrors(): Promise<void>
|
||||
declare export function timeout<T>(delay: number): Promise<T>
|
||||
}
|
||||
2
flow-typed/xo.js
vendored
2
flow-typed/xo.js
vendored
@@ -1,2 +0,0 @@
|
||||
// eslint-disable-next-line no-undef
|
||||
declare type $Dict<T, K = string> = { [K]: T }
|
||||
@@ -15,7 +15,6 @@
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.21.5",
|
||||
"exec-promise": "^0.7.0",
|
||||
"flow-bin": "^0.148.0",
|
||||
"globby": "^11.0.1",
|
||||
"handlebars": "^4.7.6",
|
||||
"husky": "^4.2.5",
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
"@babel/cli": "^7.0.0",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/preset-flow": "^7.0.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"rimraf": "^3.0.0"
|
||||
},
|
||||
|
||||
@@ -1,28 +1,4 @@
|
||||
// @flow
|
||||
|
||||
/* eslint-disable no-use-before-define */
|
||||
export type Pattern = AndPattern | OrPattern | NotPattern | ObjectPattern | ArrayPattern | ValuePattern
|
||||
/* eslint-enable no-use-before-define */
|
||||
|
||||
// all patterns must match
|
||||
type AndPattern = {| __and: Array<Pattern> |}
|
||||
|
||||
// one of the pattern must match
|
||||
type OrPattern = {| __or: Array<Pattern> |}
|
||||
|
||||
// the pattern must not match
|
||||
type NotPattern = {| __not: Pattern |}
|
||||
|
||||
// value is an object with properties matching the patterns
|
||||
type ObjectPattern = { [string]: Pattern }
|
||||
|
||||
// value is an array and each patterns must match a different item
|
||||
type ArrayPattern = Array<Pattern>
|
||||
|
||||
// value equals the pattern
|
||||
type ValuePattern = boolean | number | string
|
||||
|
||||
const match = (pattern: Pattern, value: any) => {
|
||||
const match = (pattern, value) => {
|
||||
if (Array.isArray(pattern)) {
|
||||
return (
|
||||
Array.isArray(value) &&
|
||||
@@ -40,16 +16,13 @@ const match = (pattern: Pattern, value: any) => {
|
||||
if (length === 1) {
|
||||
const [key] = keys
|
||||
if (key === '__and') {
|
||||
const andPattern: AndPattern = (pattern: any)
|
||||
return andPattern.__and.every(subpattern => match(subpattern, value))
|
||||
return pattern.__and.every(subpattern => match(subpattern, value))
|
||||
}
|
||||
if (key === '__or') {
|
||||
const orPattern: OrPattern = (pattern: any)
|
||||
return orPattern.__or.some(subpattern => match(subpattern, value))
|
||||
return pattern.__or.some(subpattern => match(subpattern, value))
|
||||
}
|
||||
if (key === '__not') {
|
||||
const notPattern: NotPattern = (pattern: any)
|
||||
return !match(notPattern.__not, value)
|
||||
return !match(pattern.__not, value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,11 +30,10 @@ const match = (pattern: Pattern, value: any) => {
|
||||
return false
|
||||
}
|
||||
|
||||
const objectPattern: ObjectPattern = (pattern: any)
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const key = keys[i]
|
||||
const subvalue = value[key]
|
||||
if (subvalue === undefined || !match(objectPattern[key], subvalue)) {
|
||||
if (subvalue === undefined || !match(pattern[key], subvalue)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -71,4 +43,4 @@ const match = (pattern: Pattern, value: any) => {
|
||||
return pattern === value
|
||||
}
|
||||
|
||||
export const createPredicate = (pattern: Pattern) => (value: any) => match(pattern, value)
|
||||
export const createPredicate = pattern => value => match(pattern, value)
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
"@babel/cli": "^7.0.0",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/preset-flow": "^7.0.0",
|
||||
"@xen-orchestra/fs": "^0.14.0",
|
||||
"babel-plugin-lodash": "^3.3.2",
|
||||
"cross-env": "^7.0.2",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"@babel/cli": "^7.0.0",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/preset-flow": "^7.0.0",
|
||||
"babel-plugin-lodash": "^3.3.2",
|
||||
"cross-env": "^7.0.2",
|
||||
"rimraf": "^3.0.0"
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
"@babel/cli": "^7.0.0",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/preset-flow": "^7.0.0",
|
||||
"babel-plugin-lodash": "^3.3.2",
|
||||
"cross-env": "^7.0.2",
|
||||
"rimraf": "^3.0.0"
|
||||
|
||||
@@ -160,7 +160,6 @@
|
||||
"@babel/plugin-proposal-pipeline-operator": "^7.0.0",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/preset-flow": "^7.0.0",
|
||||
"babel-plugin-lodash": "^3.3.2",
|
||||
"babel-plugin-transform-dev": "^2.0.1",
|
||||
"cross-env": "^7.0.2",
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
// @flow
|
||||
|
||||
// patch o: assign properties from p
|
||||
// if the value of a p property is null, delete it from o
|
||||
const patch = <T: {}>(o: T, p: $Shape<T>) => {
|
||||
const patch = (o, p) => {
|
||||
Object.keys(p).forEach(k => {
|
||||
const v: any = p[k]
|
||||
const v = p[k]
|
||||
if (v === null) {
|
||||
delete o[k]
|
||||
} else if (v !== undefined) {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import through2 from 'through2'
|
||||
import { type Readable } from 'stream'
|
||||
|
||||
const createSizeStream = (): Readable & { size: number } => {
|
||||
const createSizeStream = () => {
|
||||
const wrapper = through2((chunk, enc, cb) => {
|
||||
wrapper.size += chunk.length
|
||||
cb(null, chunk)
|
||||
|
||||
@@ -14,8 +14,6 @@ import { resolve } from 'path'
|
||||
import { utcFormat, utcParse } from 'd3-time-format'
|
||||
import { fromCallback, promisify } from 'promise-toolbox'
|
||||
|
||||
import { type SimpleIdPattern } from './utils'
|
||||
|
||||
const log = createLogger('xo:server:utils')
|
||||
|
||||
// ===================================================================
|
||||
@@ -330,7 +328,7 @@ export const getFirstPropertyName = object => {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const unboxIdsFromPattern = (pattern?: SimpleIdPattern): string[] => {
|
||||
export const unboxIdsFromPattern = pattern => {
|
||||
if (pattern === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { type Readable } from 'stream'
|
||||
|
||||
declare export function getPseudoRandomBytes(n: number): Buffer
|
||||
|
||||
declare export function safeDateFormat(timestamp: number): string
|
||||
|
||||
declare export function serializeError(error: Error): Object
|
||||
|
||||
declare export function streamToBuffer(stream: Readable): Promise<Buffer>
|
||||
|
||||
export type SimpleIdPattern = {| id: string | {| __or: string[] |} |}
|
||||
@@ -29,7 +29,6 @@ import { camelToSnakeCase, forEach, map, parseSize, pDelay, promisifyAll } from
|
||||
|
||||
import mixins from './mixins'
|
||||
import OTHER_CONFIG_TEMPLATE from './other-config-template'
|
||||
import { type DeltaVmExport } from './'
|
||||
import {
|
||||
asBoolean,
|
||||
asInteger,
|
||||
@@ -592,8 +591,8 @@ export default class Xapi extends XapiBase {
|
||||
async exportDeltaVm(
|
||||
$defer,
|
||||
$cancelToken,
|
||||
vmId: string,
|
||||
baseVmId?: string,
|
||||
vmId,
|
||||
baseVmId,
|
||||
{
|
||||
bypassVdiChainsCheck = false,
|
||||
|
||||
@@ -603,7 +602,7 @@ export default class Xapi extends XapiBase {
|
||||
disableBaseTags = false,
|
||||
snapshotNameLabel = undefined,
|
||||
} = {}
|
||||
): Promise<DeltaVmExport> {
|
||||
) {
|
||||
let vm = this.getObject(vmId)
|
||||
|
||||
// do not use the snapshot name in the delta export
|
||||
@@ -730,7 +729,7 @@ export default class Xapi extends XapiBase {
|
||||
@deferrable
|
||||
async importDeltaVm(
|
||||
$defer,
|
||||
delta: DeltaVmExport,
|
||||
delta,
|
||||
{
|
||||
deleteBase = false,
|
||||
detectBase = true,
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { type Readable } from 'stream'
|
||||
|
||||
type AugmentedReadable = Readable & {
|
||||
size?: number,
|
||||
task?: Promise<mixed>
|
||||
}
|
||||
|
||||
type MaybeArray<T> = Array<T> | T
|
||||
|
||||
export type DeltaVmExport = {|
|
||||
streams: $Dict < () => Promise < AugmentedReadable >>,
|
||||
vbds: { [ref: string]: Object },
|
||||
vdis: {
|
||||
[ref: string]: {
|
||||
$SR$uuid: string,
|
||||
snapshot_of: string,
|
||||
}
|
||||
},
|
||||
version: '1.0.0',
|
||||
vifs: { [ref: string]: Object },
|
||||
vm: Vm,
|
||||
|}
|
||||
|
||||
export type DeltaVmImport = {|
|
||||
...DeltaVmExport,
|
||||
streams: $Dict < MaybeArray < AugmentedReadable | () => Promise < AugmentedReadable >>>,
|
||||
|}
|
||||
|
||||
declare class XapiObject {
|
||||
$id: string;
|
||||
$ref: string;
|
||||
$type: string;
|
||||
}
|
||||
|
||||
type Id = string | XapiObject
|
||||
|
||||
declare export class Vbd extends XapiObject {
|
||||
type: string;
|
||||
VDI: string;
|
||||
}
|
||||
|
||||
declare export class Vdi extends XapiObject {
|
||||
$snapshot_of: Vdi;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
declare export class Vm extends XapiObject {
|
||||
$snapshots: Vm[];
|
||||
$VBDs: Vbd[];
|
||||
is_a_snapshot: boolean;
|
||||
is_a_template: boolean;
|
||||
name_label: string;
|
||||
power_state: 'Running' | 'Halted' | 'Paused' | 'Suspended';
|
||||
other_config: $Dict<string>;
|
||||
snapshot_time: number;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
declare export class Xapi {
|
||||
objects: { all: $Dict<Object> };
|
||||
|
||||
_importVm(
|
||||
cancelToken: mixed,
|
||||
stream: AugmentedReadable,
|
||||
sr?: XapiObject,
|
||||
onVmCreation?: (XapiObject) => any
|
||||
): Promise<string>;
|
||||
_setObjectProperties(
|
||||
object: XapiObject,
|
||||
properties: $Dict<mixed>
|
||||
): Promise<void>;
|
||||
_snapshotVm(cancelToken: mixed, vm: Vm, nameLabel?: string): Promise<Vm>;
|
||||
|
||||
barrier(): Promise<void>;
|
||||
barrier(ref: string): Promise<XapiObject>;
|
||||
editVm(vm: Id, $Dict<mixed>): Promise<void>;
|
||||
exportDeltaVm(
|
||||
cancelToken: mixed,
|
||||
snapshot: Id,
|
||||
baseSnapshot ?: Id,
|
||||
opts?: { fullVdisRequired?: string[] }
|
||||
): Promise<DeltaVmExport>;
|
||||
exportVm(
|
||||
cancelToken: mixed,
|
||||
vm: Vm,
|
||||
options?: { compress?: true | false | 'gzip' | 'zstd' }
|
||||
): Promise<AugmentedReadable>;
|
||||
getObject(object: Id): XapiObject;
|
||||
importDeltaVm(data: DeltaVmImport, options: Object): Promise<{ vm: Vm }>;
|
||||
importVm(stream: AugmentedReadable, options: Object): Promise<Vm>;
|
||||
shutdownVm(object: Id): Promise<void>;
|
||||
startVm(object: Id): Promise<void>;
|
||||
}
|
||||
@@ -128,7 +128,7 @@ export default class {
|
||||
}
|
||||
}
|
||||
|
||||
async authenticateUser(credentials, userData): Promise<{| user: Object, expiration?: number |}> {
|
||||
async authenticateUser(credentials, userData) {
|
||||
// don't even attempt to authenticate with empty password
|
||||
const { password } = credentials
|
||||
if (password === '') {
|
||||
|
||||
@@ -67,7 +67,7 @@ const taskTimeComparator = ({ start: s1, end: e1 }, { start: s2, end: e2 }) => {
|
||||
// }
|
||||
export default {
|
||||
getBackupNgLogs: debounceWithKey(
|
||||
async function getBackupNgLogs(runId?: string) {
|
||||
async function getBackupNgLogs(runId) {
|
||||
const [jobLogs, restoreLogs, restoreMetadataLogs] = await Promise.all([
|
||||
this.getLogs('jobs'),
|
||||
this.getLogs('restore'),
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
// @flow
|
||||
|
||||
// $FlowFixMe
|
||||
import asyncMapSettled from '@xen-orchestra/async-map/legacy'
|
||||
import type RemoteHandler from '@xen-orchestra/fs'
|
||||
import Disposable from 'promise-toolbox/Disposable'
|
||||
import { Backup } from '@xen-orchestra/backups/Backup'
|
||||
import { createLogger } from '@xen-orchestra/log'
|
||||
import { createPredicate } from 'value-matcher'
|
||||
import { decorateWith } from '@vates/decorate-with'
|
||||
import { formatVmBackups } from '@xen-orchestra/backups/formatVmBackups'
|
||||
import { forOwn, merge } from 'lodash'
|
||||
@@ -13,78 +10,17 @@ import { ImportVmBackup } from '@xen-orchestra/backups/ImportVmBackup'
|
||||
import { invalidParameters } from 'xo-common/api-errors'
|
||||
import { runBackupWorker } from '@xen-orchestra/backups/runBackupWorker'
|
||||
import { Task } from '@xen-orchestra/backups/Task'
|
||||
import { type Pattern, createPredicate } from 'value-matcher'
|
||||
|
||||
import type Logger from '../logs/loggers/abstract'
|
||||
import { type CallJob, type Executor, type Job } from '../jobs'
|
||||
import { type Schedule } from '../scheduling'
|
||||
|
||||
import { debounceWithKey, REMOVE_CACHE_ENTRY } from '../../_pDebounceWithKey'
|
||||
import { handleBackupLog } from '../../_handleBackupLog'
|
||||
import { unboxIdsFromPattern } from '../../utils'
|
||||
import { waitAll } from '../../_waitAll'
|
||||
import { type DeltaVmExport, type Xapi } from '../../xapi'
|
||||
import { type SimpleIdPattern, unboxIdsFromPattern } from '../../utils'
|
||||
|
||||
import { translateLegacyJob } from './migration'
|
||||
|
||||
const log = createLogger('xo:xo-mixins:backups-ng')
|
||||
|
||||
export type Mode = 'full' | 'delta'
|
||||
export type ReportWhen = 'always' | 'failure' | 'never'
|
||||
|
||||
type Settings = {|
|
||||
bypassVdiChainsCheck?: boolean,
|
||||
checkpointSnapshot?: boolean,
|
||||
concurrency?: number,
|
||||
deleteFirst?: boolean,
|
||||
copyRetention?: number,
|
||||
exportRetention?: number,
|
||||
offlineBackup?: boolean,
|
||||
offlineSnapshot?: boolean,
|
||||
reportRecipients?: Array<string>,
|
||||
reportWhen?: ReportWhen,
|
||||
snapshotRetention?: number,
|
||||
timeout?: number,
|
||||
vmTimeout?: number,
|
||||
|}
|
||||
|
||||
export type BackupJob = {|
|
||||
...$Exact<Job>,
|
||||
compression?: 'native' | 'zstd' | '',
|
||||
mode: Mode,
|
||||
proxy?: string,
|
||||
remotes?: SimpleIdPattern,
|
||||
settings: $Dict<Settings>,
|
||||
srs?: SimpleIdPattern,
|
||||
type: 'backup',
|
||||
vms: Pattern,
|
||||
|}
|
||||
|
||||
type MetadataBase = {|
|
||||
_filename?: string,
|
||||
jobId: string,
|
||||
scheduleId: string,
|
||||
timestamp: number,
|
||||
version: '2.0.0',
|
||||
vm: Object,
|
||||
vmSnapshot: Object,
|
||||
|}
|
||||
type MetadataDelta = {|
|
||||
...MetadataBase,
|
||||
mode: 'delta',
|
||||
vdis: $PropertyType<DeltaVmExport, 'vdis'>,
|
||||
vbds: $PropertyType<DeltaVmExport, 'vbds'>,
|
||||
vhds: { [vdiId: string]: string },
|
||||
vifs: $PropertyType<DeltaVmExport, 'vifs'>,
|
||||
|}
|
||||
type MetadataFull = {|
|
||||
...MetadataBase,
|
||||
mode: 'full',
|
||||
xva: string,
|
||||
|}
|
||||
type Metadata = MetadataDelta | MetadataFull
|
||||
|
||||
const parseVmBackupId = (id: string) => {
|
||||
const parseVmBackupId = id => {
|
||||
const i = id.indexOf('/')
|
||||
return {
|
||||
metadataFilename: id.slice(i + 1),
|
||||
@@ -92,7 +28,7 @@ const parseVmBackupId = (id: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const extractIdsFromSimplePattern = (pattern: mixed) => {
|
||||
const extractIdsFromSimplePattern = pattern => {
|
||||
if (pattern === null || typeof pattern !== 'object') {
|
||||
return
|
||||
}
|
||||
@@ -193,28 +129,11 @@ const extractIdsFromSimplePattern = (pattern: mixed) => {
|
||||
// │ └─ task.end
|
||||
// └─ job.end
|
||||
export default class BackupNg {
|
||||
_app: {
|
||||
createJob: ($Diff<BackupJob, {| id: string |}>) => Promise<BackupJob>,
|
||||
createSchedule: ($Diff<Schedule, {| id: string |}>) => Promise<Schedule>,
|
||||
deleteSchedule: (id: string) => Promise<void>,
|
||||
getAllSchedules: () => Promise<Schedule[]>,
|
||||
getRemoteHandler: (id: string) => Promise<RemoteHandler>,
|
||||
getXapi: (id: string) => Xapi,
|
||||
getJob: ((id: string, 'backup') => Promise<BackupJob>) & ((id: string, 'call') => Promise<CallJob>),
|
||||
getLogs: (namespace: string) => Promise<{ [id: string]: Object }>,
|
||||
updateJob: (($Shape<BackupJob>, ?boolean) => Promise<BackupJob>) &
|
||||
(($Shape<CallJob>, ?boolean) => Promise<CallJob>),
|
||||
removeJob: (id: string) => Promise<void>,
|
||||
worker: $Dict<any>,
|
||||
}
|
||||
_logger: Logger
|
||||
_runningRestores: Set<string>
|
||||
|
||||
get runningRestores() {
|
||||
return this._runningRestores
|
||||
}
|
||||
|
||||
constructor(app: any) {
|
||||
constructor(app) {
|
||||
this._app = app
|
||||
this._logger = undefined
|
||||
this._runningRestores = new Set()
|
||||
@@ -222,10 +141,10 @@ export default class BackupNg {
|
||||
app.hooks.on('start', async () => {
|
||||
this._logger = await app.getLogger('restore')
|
||||
|
||||
const executor: Executor = async ({ cancelToken, data, job: job_, logger, runJobId, schedule }) => {
|
||||
const executor = async ({ cancelToken, data, job: job_, logger, runJobId, schedule }) => {
|
||||
const backupsConfig = app.config.get('backups')
|
||||
|
||||
let job: BackupJob = (job_: any)
|
||||
let job = job_
|
||||
|
||||
const vmsPattern = job.vms
|
||||
|
||||
@@ -389,19 +308,15 @@ export default class BackupNg {
|
||||
})
|
||||
}
|
||||
|
||||
async createBackupNgJob(
|
||||
props: $Diff<BackupJob, {| id: string |}>,
|
||||
schedules?: $Dict<$Diff<Schedule, {| id: string |}>>
|
||||
): Promise<BackupJob> {
|
||||
async createBackupNgJob(props, schedules) {
|
||||
const app = this._app
|
||||
props.type = 'backup'
|
||||
const job: BackupJob = await app.createJob(props)
|
||||
const job = await app.createJob(props)
|
||||
|
||||
if (schedules !== undefined) {
|
||||
const { id, settings } = job
|
||||
const tmpIds = Object.keys(schedules)
|
||||
await asyncMapSettled(tmpIds, async (tmpId: string) => {
|
||||
// $FlowFixMe don't know what is the problem (JFT)
|
||||
await asyncMapSettled(tmpIds, async tmpId => {
|
||||
const schedule = schedules[tmpId]
|
||||
schedule.jobId = id
|
||||
settings[(await app.createSchedule(schedule)).id] = settings[tmpId]
|
||||
@@ -413,7 +328,7 @@ export default class BackupNg {
|
||||
return job
|
||||
}
|
||||
|
||||
async deleteBackupNgJob(id: string): Promise<void> {
|
||||
async deleteBackupNgJob(id) {
|
||||
const app = this._app
|
||||
const [schedules] = await Promise.all([app.getAllSchedules(), app.getJob(id, 'backup')])
|
||||
await Promise.all([
|
||||
@@ -426,7 +341,7 @@ export default class BackupNg {
|
||||
])
|
||||
}
|
||||
|
||||
async deleteVmBackupNg(id: string): Promise<void> {
|
||||
async deleteVmBackupNg(id) {
|
||||
const app = this._app
|
||||
const { metadataFilename, remoteId } = parseVmBackupId(id)
|
||||
const remote = await app.getRemoteWithCredentials(remoteId)
|
||||
@@ -451,7 +366,7 @@ export default class BackupNg {
|
||||
// ├─ task.start(message: 'transfer')
|
||||
// │ └─ task.end(result: { id: string, size: number })
|
||||
// └─ task.end
|
||||
async importVmBackupNg(id: string, srId: string, settings): Promise<string> {
|
||||
async importVmBackupNg(id, srId, settings) {
|
||||
const app = this._app
|
||||
const xapi = app.getXapi(srId)
|
||||
const sr = xapi.getObject(srId)
|
||||
@@ -511,7 +426,7 @@ export default class BackupNg {
|
||||
}
|
||||
} else {
|
||||
await Disposable.use(app.getBackupsRemoteAdapter(remote), async adapter => {
|
||||
const metadata: Metadata = await adapter.readVmBackupMetadata(metadataFilename)
|
||||
const metadata = await adapter.readVmBackupMetadata(metadataFilename)
|
||||
const localTaskIds = { __proto__: null }
|
||||
return Task.run(
|
||||
{
|
||||
@@ -556,7 +471,7 @@ export default class BackupNg {
|
||||
return [this, remoteId]
|
||||
}
|
||||
)
|
||||
async _listVmBackupsOnRemote(remoteId: string) {
|
||||
async _listVmBackupsOnRemote(remoteId) {
|
||||
const app = this._app
|
||||
try {
|
||||
const remote = await app.getRemoteWithCredentials(remoteId)
|
||||
@@ -589,8 +504,8 @@ export default class BackupNg {
|
||||
}
|
||||
}
|
||||
|
||||
async listVmBackupsNg(remotes: string[], _forceRefresh = false) {
|
||||
const backupsByVmByRemote: $Dict<$Dict<Metadata[]>> = {}
|
||||
async listVmBackupsNg(remotes, _forceRefresh = false) {
|
||||
const backupsByVmByRemote = {}
|
||||
|
||||
await Promise.all(
|
||||
remotes.map(async remoteId => {
|
||||
@@ -605,7 +520,7 @@ export default class BackupNg {
|
||||
return backupsByVmByRemote
|
||||
}
|
||||
|
||||
async migrateLegacyBackupJob(jobId: string) {
|
||||
async migrateLegacyBackupJob(jobId) {
|
||||
const [job, schedules] = await Promise.all([this._app.getJob(jobId, 'call'), this._app.getAllSchedules()])
|
||||
await this._app.updateJob(translateLegacyJob(job, schedules), false)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import assert from 'assert'
|
||||
|
||||
import { type BackupJob } from '../backups-ng'
|
||||
import { type CallJob } from '../jobs'
|
||||
import { type Schedule } from '../scheduling'
|
||||
|
||||
const createOr = (children: Array<any>): any => (children.length === 1 ? children[0] : { __or: children })
|
||||
const createOr = children => (children.length === 1 ? children[0] : { __or: children })
|
||||
|
||||
const methods = {
|
||||
'vm.deltaCopy': (job: CallJob, { _reportWhen: reportWhen, retention = 1, sr, vms }, schedule: Schedule) => ({
|
||||
'vm.deltaCopy': (job, { _reportWhen: reportWhen, retention = 1, sr, vms }, schedule) => ({
|
||||
mode: 'delta',
|
||||
settings: {
|
||||
'': reportWhen === undefined ? undefined : { reportWhen },
|
||||
@@ -22,11 +16,7 @@ const methods = {
|
||||
userId: job.userId,
|
||||
vms,
|
||||
}),
|
||||
'vm.rollingDeltaBackup': (
|
||||
job: CallJob,
|
||||
{ _reportWhen: reportWhen, depth = 1, retention = depth, remote, vms },
|
||||
schedule: Schedule
|
||||
) => ({
|
||||
'vm.rollingDeltaBackup': (job, { _reportWhen: reportWhen, depth = 1, retention = depth, remote, vms }, schedule) => ({
|
||||
mode: 'delta',
|
||||
remotes: { id: remote },
|
||||
settings: {
|
||||
@@ -39,9 +29,9 @@ const methods = {
|
||||
vms,
|
||||
}),
|
||||
'vm.rollingDrCopy': (
|
||||
job: CallJob,
|
||||
job,
|
||||
{ _reportWhen: reportWhen, deleteOldBackupsFirst, depth = 1, retention = depth, sr, vms },
|
||||
schedule: Schedule
|
||||
schedule
|
||||
) => ({
|
||||
mode: 'full',
|
||||
settings: {
|
||||
@@ -56,9 +46,9 @@ const methods = {
|
||||
vms,
|
||||
}),
|
||||
'vm.rollingBackup': (
|
||||
job: CallJob,
|
||||
job,
|
||||
{ _reportWhen: reportWhen, compress, depth = 1, retention = depth, remoteId, vms },
|
||||
schedule: Schedule
|
||||
schedule
|
||||
) => ({
|
||||
compression: compress ? 'native' : undefined,
|
||||
mode: 'full',
|
||||
@@ -72,11 +62,7 @@ const methods = {
|
||||
},
|
||||
vms,
|
||||
}),
|
||||
'vm.rollingSnapshot': (
|
||||
job: CallJob,
|
||||
{ _reportWhen: reportWhen, depth = 1, retention = depth, vms },
|
||||
schedule: Schedule
|
||||
) => ({
|
||||
'vm.rollingSnapshot': (job, { _reportWhen: reportWhen, depth = 1, retention = depth, vms }, schedule) => ({
|
||||
mode: 'full',
|
||||
settings: {
|
||||
'': reportWhen === undefined ? undefined : { reportWhen },
|
||||
@@ -89,7 +75,7 @@ const methods = {
|
||||
}),
|
||||
}
|
||||
|
||||
const parseParamsVector = (vector: any) => {
|
||||
const parseParamsVector = vector => {
|
||||
assert.strictEqual(vector.type, 'crossProduct')
|
||||
const { items } = vector
|
||||
assert.strictEqual(items.length, 2)
|
||||
@@ -120,7 +106,7 @@ const parseParamsVector = (vector: any) => {
|
||||
return { ...params, vms }
|
||||
}
|
||||
|
||||
export const translateLegacyJob = (job: CallJob, schedules: Schedule[]): BackupJob => {
|
||||
export const translateLegacyJob = (job, schedules) => {
|
||||
const { id } = job
|
||||
let method, schedule
|
||||
if (
|
||||
@@ -138,7 +124,6 @@ export const translateLegacyJob = (job: CallJob, schedules: Schedule[]): BackupJ
|
||||
name: params.tag || job.name,
|
||||
type: 'backup',
|
||||
userId: job.userId,
|
||||
// $FlowFixMe `method` is initialized but Flow fails to see this
|
||||
...method(job, params, schedule),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
// @flow
|
||||
import { noSuchObject } from 'xo-common/api-errors'
|
||||
|
||||
import Collection from '../collection/redis'
|
||||
import patch from '../patch'
|
||||
|
||||
type CloudConfig = {|
|
||||
id: string,
|
||||
name: string,
|
||||
template: string,
|
||||
|}
|
||||
|
||||
class CloudConfigs extends Collection {
|
||||
get(properties) {
|
||||
return super.get(properties)
|
||||
@@ -17,16 +10,7 @@ class CloudConfigs extends Collection {
|
||||
}
|
||||
|
||||
export default class {
|
||||
_app: any
|
||||
_db: {|
|
||||
add: Function,
|
||||
first: Function,
|
||||
get: Function,
|
||||
remove: Function,
|
||||
update: Function,
|
||||
|}
|
||||
|
||||
constructor(app: any) {
|
||||
constructor(app) {
|
||||
this._app = app
|
||||
const db = (this._db = new CloudConfigs({
|
||||
connection: app._redis,
|
||||
@@ -43,25 +27,25 @@ export default class {
|
||||
)
|
||||
}
|
||||
|
||||
createCloudConfig(cloudConfig: $Diff<CloudConfig, {| id: string |}>) {
|
||||
createCloudConfig(cloudConfig) {
|
||||
return this._db.add(cloudConfig).properties
|
||||
}
|
||||
|
||||
async updateCloudConfig({ id, name, template }: $Shape<CloudConfig>) {
|
||||
async updateCloudConfig({ id, name, template }) {
|
||||
const cloudConfig = await this.getCloudConfig(id)
|
||||
patch(cloudConfig, { name, template })
|
||||
return this._db.update(cloudConfig)
|
||||
}
|
||||
|
||||
deleteCloudConfig(id: string) {
|
||||
deleteCloudConfig(id) {
|
||||
return this._db.remove(id)
|
||||
}
|
||||
|
||||
getAllCloudConfigs(): Promise<Array<CloudConfig>> {
|
||||
getAllCloudConfigs() {
|
||||
return this._db.get()
|
||||
}
|
||||
|
||||
async getCloudConfig(id: string): Promise<CloudConfig> {
|
||||
async getCloudConfig(id) {
|
||||
const cloudConfig = await this._db.first(id)
|
||||
if (cloudConfig === undefined) {
|
||||
throw noSuchObject(id, 'cloud config')
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import type { Pattern } from 'value-matcher'
|
||||
|
||||
import asyncMapSettled from '@xen-orchestra/async-map/legacy'
|
||||
import emitAsync from '@xen-orchestra/emit-async'
|
||||
import { createLogger } from '@xen-orchestra/log'
|
||||
@@ -14,66 +10,12 @@ import Collection from '../../collection/redis'
|
||||
import patch from '../../patch'
|
||||
import { serializeError } from '../../utils'
|
||||
|
||||
import type Logger from '../logs/loggers/abstract'
|
||||
import { type Schedule } from '../scheduling'
|
||||
|
||||
import executeCall from './execute-call'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const log = createLogger('xo:jobs')
|
||||
|
||||
export type Job = {
|
||||
id: string,
|
||||
name: string,
|
||||
type: string,
|
||||
userId: string,
|
||||
}
|
||||
|
||||
type ParamsVector =
|
||||
| {|
|
||||
items: Array<Object>,
|
||||
type: 'crossProduct',
|
||||
|}
|
||||
| {|
|
||||
mapping: Object,
|
||||
type: 'extractProperties',
|
||||
value: Object,
|
||||
|}
|
||||
| {|
|
||||
pattern: Pattern,
|
||||
type: 'fetchObjects',
|
||||
|}
|
||||
| {|
|
||||
collection: Object,
|
||||
iteratee: Function,
|
||||
paramName?: string,
|
||||
type: 'map',
|
||||
|}
|
||||
| {|
|
||||
type: 'set',
|
||||
values: any,
|
||||
|}
|
||||
|
||||
export type CallJob = {|
|
||||
...$Exact<Job>,
|
||||
method: string,
|
||||
paramsVector: ParamsVector,
|
||||
timeout?: number,
|
||||
type: 'call',
|
||||
|}
|
||||
|
||||
export type Executor = ({|
|
||||
app: Object,
|
||||
cancelToken: any,
|
||||
data: any,
|
||||
job: Job,
|
||||
logger: Logger,
|
||||
runJobId: string,
|
||||
schedule?: Schedule,
|
||||
session: Object,
|
||||
|}) => Promise<any>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const normalize = job => {
|
||||
@@ -94,7 +36,7 @@ const normalize = job => {
|
||||
return job
|
||||
}
|
||||
|
||||
const serialize = (job: {| [string]: any |}) => {
|
||||
const serialize = job => {
|
||||
Object.keys(job).forEach(key => {
|
||||
const value = job[key]
|
||||
if (typeof value !== 'string') {
|
||||
@@ -105,15 +47,15 @@ const serialize = (job: {| [string]: any |}) => {
|
||||
}
|
||||
|
||||
class JobsDb extends Collection {
|
||||
async create(job): Promise<Job> {
|
||||
return normalize((await this.add(serialize((job: any)))).properties)
|
||||
async create(job) {
|
||||
return normalize((await this.add(serialize(job))).properties)
|
||||
}
|
||||
|
||||
async save(job): Promise<void> {
|
||||
await this.update(serialize((job: any)))
|
||||
async save(job) {
|
||||
await this.update(serialize(job))
|
||||
}
|
||||
|
||||
async get(properties): Promise<Array<Job>> {
|
||||
async get(properties) {
|
||||
const jobs = await super.get(properties)
|
||||
jobs.forEach(normalize)
|
||||
return jobs
|
||||
@@ -123,18 +65,11 @@ class JobsDb extends Collection {
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
export default class Jobs {
|
||||
_app: any
|
||||
_executors: { __proto__: null, [string]: Executor }
|
||||
_jobs: JobsDb
|
||||
_logger: Logger
|
||||
_runningJobs: { __proto__: null, [string]: string }
|
||||
_runs: { __proto__: null, [string]: () => void }
|
||||
|
||||
get runningJobs() {
|
||||
return this._runningJobs
|
||||
}
|
||||
|
||||
constructor(app: any) {
|
||||
constructor(app) {
|
||||
this._app = app
|
||||
const executors = (this._executors = { __proto__: null })
|
||||
const jobsDb = (this._jobs = new JobsDb({
|
||||
@@ -180,15 +115,14 @@ export default class Jobs {
|
||||
)
|
||||
}
|
||||
|
||||
cancelJobRun(id: string) {
|
||||
cancelJobRun(id) {
|
||||
const run = this._runs[id]
|
||||
if (run !== undefined) {
|
||||
return run.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
async getAllJobs(type?: string): Promise<Array<Job>> {
|
||||
// $FlowFixMe don't know what is the problem (JFT)
|
||||
async getAllJobs(type) {
|
||||
const jobs = await this._jobs.get()
|
||||
const runningJobs = this._runningJobs
|
||||
const result = []
|
||||
@@ -201,7 +135,7 @@ export default class Jobs {
|
||||
return result
|
||||
}
|
||||
|
||||
async getJob(id: string, type?: string): Promise<Job> {
|
||||
async getJob(id, type) {
|
||||
let job = await this._jobs.first(id)
|
||||
if (job === undefined || (type !== undefined && job.properties.type !== type)) {
|
||||
throw noSuchObject(id, 'job')
|
||||
@@ -213,11 +147,11 @@ export default class Jobs {
|
||||
return job
|
||||
}
|
||||
|
||||
createJob(job: $Diff<Job, {| id: string |}>): Promise<Job> {
|
||||
createJob(job) {
|
||||
return this._jobs.create(job)
|
||||
}
|
||||
|
||||
async updateJob(job: $Shape<Job>, merge: boolean = true) {
|
||||
async updateJob(job, merge = true) {
|
||||
if (merge) {
|
||||
const { id, ...props } = job
|
||||
job = await this.getJob(id)
|
||||
@@ -226,7 +160,7 @@ export default class Jobs {
|
||||
return /* await */ this._jobs.save(job)
|
||||
}
|
||||
|
||||
registerJobExecutor(type: string, executor: Executor): void {
|
||||
registerJobExecutor(type, executor) {
|
||||
const executors = this._executors
|
||||
if (type in executors) {
|
||||
throw new Error(`there is already a job executor for type ${type}`)
|
||||
@@ -234,7 +168,7 @@ export default class Jobs {
|
||||
executors[type] = executor
|
||||
}
|
||||
|
||||
async removeJob(id: string) {
|
||||
async removeJob(id) {
|
||||
const promises = [this._jobs.remove(id)]
|
||||
;(await this._app.getAllSchedules()).forEach(schedule => {
|
||||
if (schedule.jobId === id) {
|
||||
@@ -245,7 +179,7 @@ export default class Jobs {
|
||||
}
|
||||
|
||||
@defer
|
||||
async _runJob($defer, job: Job, schedule?: Schedule, data_?: any) {
|
||||
async _runJob($defer, job, schedule, data_) {
|
||||
const logger = this._logger
|
||||
const { id, type } = job
|
||||
|
||||
@@ -253,7 +187,6 @@ export default class Jobs {
|
||||
data:
|
||||
type === 'backup' || type === 'metadataBackup'
|
||||
? {
|
||||
// $FlowFixMe only defined for BackupJob
|
||||
mode: job.mode,
|
||||
reportWhen: job.settings['']?.reportWhen ?? 'failure',
|
||||
}
|
||||
@@ -264,7 +197,6 @@ export default class Jobs {
|
||||
jobName: job.name,
|
||||
proxyId: job.proxy,
|
||||
scheduleId: schedule?.id,
|
||||
// $FlowFixMe only defined for CallJob
|
||||
key: job.key,
|
||||
type,
|
||||
})
|
||||
@@ -393,7 +325,7 @@ export default class Jobs {
|
||||
}
|
||||
}
|
||||
|
||||
async runJobSequence(idSequence: Array<string>, schedule?: Schedule, data?: any) {
|
||||
async runJobSequence(idSequence, schedule, data) {
|
||||
const jobs = await Promise.all(idSequence.map(id => this.getJob(id)))
|
||||
|
||||
for (const job of jobs) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import asyncMapSettled from '@xen-orchestra/async-map/legacy'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import Disposable from 'promise-toolbox/Disposable'
|
||||
@@ -11,34 +10,12 @@ import { Task } from '@xen-orchestra/backups/Task'
|
||||
import { debounceWithKey, REMOVE_CACHE_ENTRY } from '../_pDebounceWithKey'
|
||||
import { handleBackupLog } from '../_handleBackupLog'
|
||||
import { waitAll } from '../_waitAll'
|
||||
import { type Xapi } from '../xapi'
|
||||
import { serializeError, type SimpleIdPattern, unboxIdsFromPattern } from '../utils'
|
||||
|
||||
import { type Executor, type Job } from './jobs'
|
||||
import { type Schedule } from './scheduling'
|
||||
import { serializeError, unboxIdsFromPattern } from '../utils'
|
||||
|
||||
const log = createLogger('xo:xo-mixins:metadata-backups')
|
||||
|
||||
const METADATA_BACKUP_JOB_TYPE = 'metadataBackup'
|
||||
|
||||
type ReportWhen = 'always' | 'failure' | 'never'
|
||||
|
||||
type Settings = {|
|
||||
reportWhen?: ReportWhen,
|
||||
retentionPoolMetadata?: number,
|
||||
retentionXoMetadata?: number,
|
||||
|}
|
||||
|
||||
type MetadataBackupJob = {
|
||||
...$Exact<Job>,
|
||||
pools?: SimpleIdPattern,
|
||||
proxy?: string,
|
||||
remotes: SimpleIdPattern,
|
||||
settings: $Dict<Settings>,
|
||||
type: METADATA_BACKUP_JOB_TYPE,
|
||||
xoMetadata?: boolean,
|
||||
}
|
||||
|
||||
// metadata.json
|
||||
//
|
||||
// {
|
||||
@@ -79,21 +56,11 @@ type MetadataBackupJob = {
|
||||
// │ └─ task.end
|
||||
// └─ job.end
|
||||
export default class metadataBackup {
|
||||
_app: {
|
||||
createJob: ($Diff<MetadataBackupJob, {| id: string |}>) => Promise<MetadataBackupJob>,
|
||||
createSchedule: ($Diff<Schedule, {| id: string |}>) => Promise<Schedule>,
|
||||
deleteSchedule: (id: string) => Promise<void>,
|
||||
getXapi: (id: string) => Xapi,
|
||||
getJob: (id: string, ?METADATA_BACKUP_JOB_TYPE) => Promise<MetadataBackupJob>,
|
||||
updateJob: ($Shape<MetadataBackupJob>, ?boolean) => Promise<MetadataBackupJob>,
|
||||
removeJob: (id: string) => Promise<void>,
|
||||
}
|
||||
|
||||
get runningMetadataRestores() {
|
||||
return this._runningMetadataRestores
|
||||
}
|
||||
|
||||
constructor(app: any) {
|
||||
constructor(app) {
|
||||
this._app = app
|
||||
this._logger = undefined
|
||||
this._runningMetadataRestores = new Set()
|
||||
@@ -109,8 +76,8 @@ export default class metadataBackup {
|
||||
})
|
||||
}
|
||||
|
||||
async _executor({ cancelToken, job: job_, logger, runJobId, schedule }): Executor {
|
||||
const job: MetadataBackupJob = cloneDeep((job_: any))
|
||||
async _executor({ cancelToken, job: job_, logger, runJobId, schedule }) {
|
||||
const job = cloneDeep(job_)
|
||||
const scheduleSettings = job.settings[schedule.id]
|
||||
|
||||
// it also replaces null retentions introduced by the commit
|
||||
@@ -220,13 +187,10 @@ export default class metadataBackup {
|
||||
}
|
||||
}
|
||||
|
||||
async createMetadataBackupJob(
|
||||
props: $Diff<MetadataBackupJob, {| id: string |}>,
|
||||
schedules: $Dict<$Diff<Schedule, {| id: string |}>>
|
||||
): Promise<MetadataBackupJob> {
|
||||
async createMetadataBackupJob(props, schedules) {
|
||||
const app = this._app
|
||||
|
||||
const job: MetadataBackupJob = await app.createJob({
|
||||
const job = await app.createJob({
|
||||
...props,
|
||||
type: METADATA_BACKUP_JOB_TYPE,
|
||||
})
|
||||
@@ -245,7 +209,7 @@ export default class metadataBackup {
|
||||
return job
|
||||
}
|
||||
|
||||
async deleteMetadataBackupJob(id: string): Promise<void> {
|
||||
async deleteMetadataBackupJob(id) {
|
||||
const app = this._app
|
||||
const [schedules] = await Promise.all([
|
||||
app.getAllSchedules(),
|
||||
@@ -349,7 +313,7 @@ export default class metadataBackup {
|
||||
// [remote ID]: poolBackups
|
||||
// }
|
||||
// }
|
||||
async listMetadataBackups(remoteIds: string[]) {
|
||||
async listMetadataBackups(remoteIds) {
|
||||
const xo = {}
|
||||
const pool = {}
|
||||
await Promise.all(
|
||||
@@ -381,7 +345,7 @@ export default class metadataBackup {
|
||||
//
|
||||
// task.start(message: 'restore', data: <Metadata />)
|
||||
// └─ task.end
|
||||
async restoreMetadataBackup(id: string) {
|
||||
async restoreMetadataBackup(id) {
|
||||
const app = this._app
|
||||
const logger = this._logger
|
||||
const [remoteId, ...path] = id.split('/')
|
||||
@@ -468,7 +432,7 @@ export default class metadataBackup {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteMetadataBackup(id: string) {
|
||||
async deleteMetadataBackup(id) {
|
||||
const app = this._app
|
||||
const [remoteId, ...path] = id.split('/')
|
||||
const backupId = path.join('/')
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import asyncMapSettled from '@xen-orchestra/async-map/legacy'
|
||||
import { createSchedule } from '@xen-orchestra/cron'
|
||||
import { ignoreErrors } from 'promise-toolbox'
|
||||
@@ -9,16 +7,6 @@ import { noSuchObject } from 'xo-common/api-errors'
|
||||
import Collection from '../collection/redis'
|
||||
import patch from '../patch'
|
||||
|
||||
export type Schedule = {|
|
||||
cron: string,
|
||||
enabled: boolean,
|
||||
id: string,
|
||||
jobId: string,
|
||||
name: string,
|
||||
timezone?: string,
|
||||
userId: string,
|
||||
|}
|
||||
|
||||
const normalize = schedule => {
|
||||
const { enabled } = schedule
|
||||
if (typeof enabled !== 'boolean') {
|
||||
@@ -40,17 +28,7 @@ class Schedules extends Collection {
|
||||
}
|
||||
|
||||
export default class Scheduling {
|
||||
_app: any
|
||||
_db: {|
|
||||
add: Function,
|
||||
first: Function,
|
||||
get: Function,
|
||||
remove: Function,
|
||||
update: Function,
|
||||
|}
|
||||
_runs: { __proto__: null, [string]: () => void }
|
||||
|
||||
constructor(app: any) {
|
||||
constructor(app) {
|
||||
this._app = app
|
||||
|
||||
const db = (this._db = new Schedules({
|
||||
@@ -94,7 +72,7 @@ export default class Scheduling {
|
||||
})
|
||||
}
|
||||
|
||||
async createSchedule({ cron, enabled, jobId, name = '', timezone, userId }: $Diff<Schedule, {| id: string |}>) {
|
||||
async createSchedule({ cron, enabled, jobId, name = '', timezone, userId }) {
|
||||
const schedule = (
|
||||
await this._db.add({
|
||||
cron,
|
||||
@@ -109,7 +87,7 @@ export default class Scheduling {
|
||||
return schedule
|
||||
}
|
||||
|
||||
async getSchedule(id: string): Promise<Schedule> {
|
||||
async getSchedule(id) {
|
||||
const schedule = await this._db.first(id)
|
||||
if (schedule === undefined) {
|
||||
throw noSuchObject(id, 'schedule')
|
||||
@@ -117,16 +95,16 @@ export default class Scheduling {
|
||||
return schedule.properties
|
||||
}
|
||||
|
||||
async getAllSchedules(): Promise<Array<Schedule>> {
|
||||
async getAllSchedules() {
|
||||
return this._db.get()
|
||||
}
|
||||
|
||||
async deleteSchedule(id: string) {
|
||||
async deleteSchedule(id) {
|
||||
this._stop(id)
|
||||
await this._db.remove(id)
|
||||
}
|
||||
|
||||
async updateSchedule({ cron, enabled, id, jobId, name, timezone, userId }: $Shape<Schedule>) {
|
||||
async updateSchedule({ cron, enabled, id, jobId, name, timezone, userId }) {
|
||||
const schedule = await this.getSchedule(id)
|
||||
patch(schedule, { cron, enabled, jobId, name, timezone, userId })
|
||||
|
||||
@@ -135,7 +113,7 @@ export default class Scheduling {
|
||||
await this._db.update(schedule)
|
||||
}
|
||||
|
||||
_start(schedule: Schedule) {
|
||||
_start(schedule) {
|
||||
const { id } = schedule
|
||||
|
||||
this._stop(id)
|
||||
@@ -147,7 +125,7 @@ export default class Scheduling {
|
||||
}
|
||||
}
|
||||
|
||||
_stop(id: string) {
|
||||
_stop(id) {
|
||||
const runs = this._runs
|
||||
if (id in runs) {
|
||||
runs[id]()
|
||||
|
||||
29
yarn.lock
29
yarn.lock
@@ -497,13 +497,6 @@
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.8.3"
|
||||
|
||||
"@babel/plugin-syntax-flow@^7.12.13":
|
||||
version "7.12.13"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.12.13.tgz#5df9962503c0a9c918381c929d51d4d6949e7e86"
|
||||
integrity sha512-J/RYxnlSLXZLVR7wTRsozxKT8qbsx1mNKJzXEEjQ0Kjx1ZACcyHgbanNWNCFtc36IzuWhYWPpvJFFoexoOWFmA==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.12.13"
|
||||
|
||||
"@babel/plugin-syntax-function-bind@^7.12.13":
|
||||
version "7.12.13"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-function-bind/-/plugin-syntax-function-bind-7.12.13.tgz#21e32e233c10258b0437ab8f9188ea9d8bddc0e5"
|
||||
@@ -675,14 +668,6 @@
|
||||
"@babel/helper-builder-binary-assignment-operator-visitor" "^7.12.13"
|
||||
"@babel/helper-plugin-utils" "^7.12.13"
|
||||
|
||||
"@babel/plugin-transform-flow-strip-types@^7.13.0":
|
||||
version "7.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.13.0.tgz#58177a48c209971e8234e99906cb6bd1122addd3"
|
||||
integrity sha512-EXAGFMJgSX8gxWD7PZtW/P6M+z74jpx3wm/+9pn+c2dOawPpBkUX7BrfyPvo6ZpXbgRIEuwgwDb/MGlKvu2pOg==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.13.0"
|
||||
"@babel/plugin-syntax-flow" "^7.12.13"
|
||||
|
||||
"@babel/plugin-transform-for-of@^7.13.0":
|
||||
version "7.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz#c799f881a8091ac26b54867a845c3e97d2696062"
|
||||
@@ -971,15 +956,6 @@
|
||||
core-js-compat "^3.9.0"
|
||||
semver "^6.3.0"
|
||||
|
||||
"@babel/preset-flow@^7.0.0":
|
||||
version "7.13.13"
|
||||
resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.13.13.tgz#a61a1c149b3f77589d795287744393444d5cdd9e"
|
||||
integrity sha512-MDtwtamMifqq3R2mC7l3A3uFalUb3NH5TIBQWjN/epEPlZktcLq4se3J+ivckKrLMGsR7H9LW8+pYuIUN9tsKg==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.13.0"
|
||||
"@babel/helper-validator-option" "^7.12.17"
|
||||
"@babel/plugin-transform-flow-strip-types" "^7.13.0"
|
||||
|
||||
"@babel/preset-modules@^0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e"
|
||||
@@ -7166,11 +7142,6 @@ flatted@^3.1.0:
|
||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469"
|
||||
integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==
|
||||
|
||||
flow-bin@^0.148.0:
|
||||
version "0.148.0"
|
||||
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.148.0.tgz#1d264606dbb4d6e6070cc98a775e21dcd64e6890"
|
||||
integrity sha512-7Cx6BUm8UAlbqtYJNYXdMrh900MQhNV+SjtBxZuWN7UmlVG4tIRNzNLEOjNnj2DN2vcL1wfI5IlSUXnws/QCEw==
|
||||
|
||||
flush-write-stream@^1.0.0, flush-write-stream@^1.0.2:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
|
||||
|
||||
Reference in New Issue
Block a user