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:
Julien Fontanet
2021-04-21 16:54:33 +02:00
parent c262dd06e6
commit 78c0f2c7e9
32 changed files with 145 additions and 650 deletions

View File

@@ -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"
},

View File

@@ -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",

View File

@@ -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')
}
}

View File

@@ -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) => {

View File

@@ -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]

View File

@@ -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"

View File

@@ -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
View File

@@ -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[]
}

View File

@@ -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
View File

@@ -1,2 +0,0 @@
// eslint-disable-next-line no-undef
declare type $Dict<T, K = string> = { [K]: T }

View File

@@ -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",

View File

@@ -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"
},

View File

@@ -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)

View File

@@ -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",

View File

@@ -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"

View File

@@ -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"

View File

@@ -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",

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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 []
}

View File

@@ -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[] |} |}

View File

@@ -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,

View File

@@ -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>;
}

View File

@@ -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 === '') {

View File

@@ -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'),

View File

@@ -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)
}

View File

@@ -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),
}
}

View File

@@ -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')

View File

@@ -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) {

View File

@@ -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('/')

View File

@@ -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]()

View File

@@ -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"