diff --git a/packages/xo-server-backup-reports/.editorconfig b/packages/xo-server-backup-reports/.editorconfig new file mode 100644 index 000000000..da21ef4c5 --- /dev/null +++ b/packages/xo-server-backup-reports/.editorconfig @@ -0,0 +1,65 @@ +# http://EditorConfig.org +# +# Julien Fontanet's configuration +# https://gist.github.com/julien-f/8096213 + +# Top-most EditorConfig file. +root = true + +# Common config. +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespaces = true + +# CoffeeScript +# +# https://github.com/polarmobile/coffeescript-style-guide/blob/master/README.md +[*.{,lit}coffee] +indent_size = 2 +indent_style = space + +# Markdown +[*.{md,mdwn,mdown,markdown}] +indent_size = 4 +indent_style = space + +# Package.json +# +# This indentation style is the one used by npm. +[/package.json] +indent_size = 2 +indent_style = space + +# Jade +[*.jade] +indent_size = 2 +indent_style = space + +# JavaScript +# +# Two spaces seems to be the standard most common style, at least in +# Node.js (http://nodeguide.com/style.html#tabs-vs-spaces). +[*.js] +indent_size = 2 +indent_style = space + +# Less +[*.less] +indent_size = 2 +indent_style = space + +# Sass +# +# Style used for http://libsass.com +[*.s[ac]ss] +indent_size = 2 +indent_style = space + +# YAML +# +# Only spaces are allowed. +[*.yaml] +indent_size = 2 +indent_style = space diff --git a/packages/xo-server-backup-reports/.gitignore b/packages/xo-server-backup-reports/.gitignore new file mode 100644 index 000000000..827e4e420 --- /dev/null +++ b/packages/xo-server-backup-reports/.gitignore @@ -0,0 +1,7 @@ +/dist/ +/node_modules/ + +npm-debug.log +npm-debug.log.* +pnpm-debug.log +pnpm-debug.log.* diff --git a/packages/xo-server-backup-reports/.npmignore b/packages/xo-server-backup-reports/.npmignore new file mode 100644 index 000000000..c31ee82cb --- /dev/null +++ b/packages/xo-server-backup-reports/.npmignore @@ -0,0 +1,10 @@ +/examples/ +example.js +example.js.map +*.example.js +*.example.js.map + +/test/ +/tests/ +*.spec.js +*.spec.js.map diff --git a/packages/xo-server-backup-reports/.travis.yml b/packages/xo-server-backup-reports/.travis.yml new file mode 100644 index 000000000..1a582c426 --- /dev/null +++ b/packages/xo-server-backup-reports/.travis.yml @@ -0,0 +1,9 @@ +language: node_js +node_js: + - stable + - 6 + - 4 + +# Use containers. +# http://docs.travis-ci.com/user/workers/container-based-infrastructure/ +sudo: false diff --git a/packages/xo-server-backup-reports/README.md b/packages/xo-server-backup-reports/README.md new file mode 100644 index 000000000..abc21a615 --- /dev/null +++ b/packages/xo-server-backup-reports/README.md @@ -0,0 +1,52 @@ +# xo-server-backup-reports [![Build Status](https://api.travis-ci.org/vatesfr/xo-server-backup-reports.png?branch=master)](https://travis-ci.org/vatesfr/xo-server-backup-reports) + +> Backup reports plugin for XO-Server + +XO-Server plugin which sends email reports and Xmpp messages when backup jobs are done. + +## Install + +Installation of the [npm package](https://npmjs.org/package/xo-server-backup-reports): + +``` +> npm install --global xo-server-backup-reports +``` + +## Usage + +Like all other xo-server plugins, it can be configured directly via +the web iterface, see [the plugin documentation](https://xen-orchestra.com/docs/plugins.html). + +## Development + +``` +# Install dependencies +> npm install + +# Run the tests +> npm test + +# Continuously compile +> npm run dev + +# Continuously run the tests +> npm run dev-test + +# Build for production (automatically called by npm install) +> npm run build +``` + +## Contributions + +Contributions are *very* welcomed, either on the documentation or on +the code. + +You may: + +- report any [issue](https://github.com/vatesfr/xo-server-backup-reports/issues) + you've encountered; +- fork and create a pull request. + +## License + +AGPL3 © [Vates SAS](http://vates.fr) diff --git a/packages/xo-server-backup-reports/package.json b/packages/xo-server-backup-reports/package.json new file mode 100644 index 000000000..975fa9052 --- /dev/null +++ b/packages/xo-server-backup-reports/package.json @@ -0,0 +1,91 @@ +{ + "name": "xo-server-backup-reports", + "version": "0.6.0", + "license": "AGPL-3.0", + "description": "Backup reports plugin for XO-Server", + "keywords": [ + "backup", + "email", + "mail", + "orchestra", + "plugin", + "report", + "reports", + "xen", + "xen-orchestra", + "xo-server" + ], + "homepage": "https://github.com/vatesfr/xo-server-backup-reports", + "bugs": "https://github.com/vatesfr/xo-server-backup-reports/issues", + "repository": { + "type": "git", + "url": "https://github.com/vatesfr/xo-server-backup-reports" + }, + "author": { + "name": "Julien Fontanet", + "email": "julien.fontanet@isonoe.net" + }, + "preferGlobal": false, + "main": "dist/", + "bin": {}, + "files": [ + "dist/" + ], + "engines": { + "node": ">=4" + }, + "dependencies": { + "lodash": "^4.13.1", + "moment": "^2.13.0" + }, + "devDependencies": { + "babel-cli": "^6.18.0", + "babel-eslint": "^7.1.1", + "babel-plugin-lodash": "^3.2.10", + "babel-preset-env": "^1.0.0", + "babel-preset-stage-0": "^6.16.0", + "cross-env": "^3.1.3", + "dependency-check": "^2.6.0", + "ghooks": "^1.3.2", + "rimraf": "^2.5.4", + "standard": "^8.5.0" + }, + "scripts": { + "build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/", + "clean": "rimraf dist/", + "depcheck": "dependency-check ./package.json", + "dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/", + "lint": "standard", + "posttest": "npm run lint && npm run depcheck", + "prebuild": "npm run clean", + "predev": "npm run clean", + "prepublish": "npm run build" + }, + "babel": { + "plugins": [ + "lodash" + ], + "presets": [ + [ + "env", + { + "targets": { + "node": 4 + } + } + ], + "stage-0" + ] + }, + "standard": { + "ignore": [ + "dist" + ], + "parser": "babel-eslint" + }, + "config": { + "ghooks": { + "commit-msg": "npm test" + } + } +} diff --git a/packages/xo-server-backup-reports/src/index.js b/packages/xo-server-backup-reports/src/index.js new file mode 100644 index 000000000..5ee8474ff --- /dev/null +++ b/packages/xo-server-backup-reports/src/index.js @@ -0,0 +1,178 @@ +import moment from 'moment' +import { forEach } from 'lodash' + +export const configurationSchema = { + type: 'object', + + properties: { + toMails: { + type: 'array', + title: 'mails', + description: 'an array of recipients (mails)', + + items: { + type: 'string' + }, + minItems: 1 + }, + toXmpp: { + type: 'array', + title: 'xmpp address', + description: 'an array of recipients (xmpp)', + + items: { + type: 'string' + }, + minItems: 1 + } + } +} + +// =================================================================== + +const logError = e => { + console.error('backup report error:', e) +} + +class BackupReportsXoPlugin { + constructor (xo) { + this._xo = xo + this._report = ::this._wrapper + } + + configure ({ toMails, toXmpp }) { + this._mailsReceivers = toMails + this._xmppReceivers = toXmpp + } + + load () { + this._xo.on('job:terminated', this._report) + } + + unload () { + this._xo.removeListener('job:terminated', this._report) + } + + _wrapper (status) { + return new Promise(resolve => resolve(this._listener(status))).catch(logError) + } + + _listener (status) { + let nSuccess = 0 + let nCalls = 0 + let reportWhen + + const text = [] + const nagiosText = [] + + forEach(status.calls, call => { + // Ignore call if it's not a Backup a Snapshot or a Disaster Recovery. + if (call.method !== 'vm.deltaCopy' && + call.method !== 'vm.rollingDeltaBackup' && + call.method !== 'vm.rollingDrCopy' && + call.method !== 'vm.rollingSnapshot' && + call.method !== 'vm.rollingBackup') { + return + } + + reportWhen = call.params._reportWhen + + if (reportWhen === 'never') { + return + } + + nCalls++ + if (!call.error) { + nSuccess++ + } + + let vm + + try { + vm = this._xo.getObject(call.params.id || call.params.vm) + } catch (e) {} + + const start = moment(call.start) + const end = moment(call.end) + const duration = moment.duration(end - start).humanize() + + text.push([ + `### VM : ${vm ? vm.name_label : 'undefined'}`, + ` - UUID: ${vm ? vm.uuid : 'undefined'}`, + call.error + ? ` - Status: Failure\n - Error: ${call.error.message}` + : ' - Status: Success', + ` - Start time: ${String(start)}`, + ` - End time: ${String(end)}`, + ` - Duration: ${duration}` + ].join('\n')) + + if (call.error) { + nagiosText.push( + `[ ${vm ? vm.name_label : 'undefined'} : ${call.error.message} ]` + ) + } + }) + + // No backup calls. + if (nCalls === 0) { + return + } + + const globalSuccess = nSuccess === nCalls + if (globalSuccess && ( + reportWhen === 'fail' || // xo-web < 5 + reportWhen === 'failure' // xo-web >= 5 + )) { + return + } + + const start = moment(status.start) + const end = moment(status.end) + const duration = moment.duration(end - start).humanize() + let method = status.calls[Object.keys(status.calls)[0]].method + method = method.slice(method.indexOf('.') + 1) + .replace(/([A-Z])/g, ' $1').replace(/^./, letter => letter.toUpperCase()) // humanize + const tag = status.calls[Object.keys(status.calls)[0]].params.tag + + // Global status. + text.unshift([ + `## Global status for "${tag}" (${method}): ${globalSuccess ? 'Success' : 'Fail'}`, + ` - Start time: ${String(start)}`, + ` - End time: ${String(end)}`, + ` - Duration: ${duration}`, + ` - Successful backed up VM number: ${nSuccess}`, + ` - Failed backed up VM: ${nCalls - nSuccess}`, + '' + ].join('\n')) + + const markdown = text.join('\n') + const markdownNagios = nagiosText.join(' ') + + // TODO : Handle errors when `sendEmail` isn't present. (Plugin dependencies) + + const xo = this._xo + return Promise.all([ + xo.sendEmail && xo.sendEmail({ + to: this._mailsReceivers, + subject: `[Xen Orchestra][${globalSuccess ? 'Success' : 'Failure'}] Backup report for ${tag}`, + markdown + }), + xo.sendToXmppClient && xo.sendToXmppClient({ + to: this._xmppReceivers, + message: markdown + }), + xo.sendSlackMessage && xo.sendSlackMessage({ + message: markdown + }), + xo.sendPassiveCheck && xo.sendPassiveCheck({ + status: globalSuccess ? 0 : 2, + message: globalSuccess ? `[Xen Orchestra] [Success] Backup report for ${tag}` : `[Xen Orchestra] [Failure] Backup report for ${tag} - VMs : ${markdownNagios}` + }) + ]) + } +} + +// =================================================================== + +export default ({ xo }) => new BackupReportsXoPlugin(xo)