@cloudant/couchbackup
Advanced tools
Comparing version 2.3.0-SNAPSHOT.127 to 2.3.0-SNAPSHOT.128
255
app.js
@@ -22,9 +22,10 @@ // Copyright © 2017, 2018 IBM Corp. All rights reserved. | ||
const backupFull = require('./includes/backup.js'); | ||
const defaults = require('./includes/config.js').apiDefaults(); | ||
const error = require('./includes/error.js'); | ||
const request = require('./includes/request.js'); | ||
const restoreInternal = require('./includes/restore.js'); | ||
const backupShallow = require('./includes/shallowbackup.js'); | ||
const backupFull = require('./includes/backup.js'); | ||
const defaults = require('./includes/config.js').apiDefaults(); | ||
const debug = require('debug')('couchbackup:app'); | ||
const events = require('events'); | ||
const debug = require('debug')('couchbackup:app'); | ||
const error = require('./includes/error.js'); | ||
const fs = require('fs'); | ||
@@ -140,2 +141,29 @@ const legacyUrl = require('url'); | ||
/* | ||
Check the referenced database exists and that the credentials used have | ||
visibility. Callback with a fatal error if there is a problem with the DB. | ||
@param {string} db - database object | ||
@param {function(err)} callback - error is undefined if DB exists | ||
*/ | ||
function proceedIfDbValid(db, callback) { | ||
db.head('', function(err) { | ||
err = error.convertResponseError(err, function(err) { | ||
if (err && err.statusCode === 404) { | ||
// Override the error type and mesasge for the DB not found case | ||
var msg = `Database ${db.config.url.replace(/\/\/.+@/g, '//****:****@')}` + | ||
`/${db.config.db} does not exist. ` + | ||
'Check the URL and database name have been specified correctly.'; | ||
var noDBErr = new Error(msg); | ||
noDBErr.name = 'DatabaseNotFound'; | ||
return noDBErr; | ||
} else { | ||
// Delegate to the default error factory if it wasn't a 404 | ||
return error.convertResponseError(err); | ||
} | ||
}); | ||
// Callback with or without (i.e. undefined) error | ||
callback(err); | ||
}); | ||
} | ||
module.exports = { | ||
@@ -167,13 +195,3 @@ | ||
} | ||
opts = Object.assign({}, defaults, opts); | ||
var backup = null; | ||
if (opts.mode === 'shallow') { | ||
backup = backupShallow; | ||
} else { // full mode | ||
backup = backupFull; | ||
} | ||
const ee = new events.EventEmitter(); | ||
// if there is an error writing to the stream, call the completion | ||
@@ -186,64 +204,88 @@ // callback with the error set | ||
// If resuming write a newline as it's possible one would be missing from | ||
// an interruption of the previous backup. If the backup was clean this | ||
// will cause an empty line that will be gracefully handled by the restore. | ||
if (opts.resume) { | ||
targetStream.write('\n'); | ||
} | ||
opts = Object.assign({}, defaults, opts); | ||
// Get the event emitter from the backup process so we can handle events | ||
// before passing them on to the app's event emitter if needed. | ||
const internalEE = backup(srcUrl, opts); | ||
addEventListener(listenerErrorIndicator, internalEE, 'changes', function(batch) { | ||
ee.emit('changes', batch); | ||
}); | ||
addEventListener(listenerErrorIndicator, internalEE, 'received', function(obj, q, logCompletedBatch) { | ||
// this may be too verbose to have as well as the "backed up" message | ||
// debug(' received batch', obj.batch, ' docs: ', obj.total, 'Time', obj.time); | ||
// Callback to emit the written event when the content is flushed | ||
function writeFlushed() { | ||
ee.emit('written', {total: obj.total, time: obj.time, batch: obj.batch}); | ||
if (logCompletedBatch) { | ||
logCompletedBatch(obj.batch); | ||
const ee = new events.EventEmitter(); | ||
// Set up the DB client | ||
const backupDB = request.client(srcUrl, opts); | ||
// Validate the DB exists, before proceeding to backup | ||
proceedIfDbValid(backupDB, function(err) { | ||
if (err) { | ||
if (err.name === 'DatabaseNotFound') { | ||
err.message = `${err.message} Ensure the backup source database exists.`; | ||
} | ||
debug(' backed up batch', obj.batch, ' docs: ', obj.total, 'Time', obj.time); | ||
// Didn't exist, or another fatal error, exit | ||
callback(err); | ||
return; | ||
} | ||
// Write the received content to the targetStream | ||
const continueWriting = targetStream.write(JSON.stringify(obj.data) + '\n', | ||
'utf8', | ||
writeFlushed); | ||
if (!continueWriting) { | ||
// The buffer was full, pause the queue to stop the writes until we | ||
// get a drain event | ||
if (q && !q.isPaused) { | ||
q.pause(); | ||
targetStream.once('drain', function() { | ||
q.resume(); | ||
}); | ||
} | ||
var backup = null; | ||
if (opts.mode === 'shallow') { | ||
backup = backupShallow; | ||
} else { // full mode | ||
backup = backupFull; | ||
} | ||
}); | ||
// For errors we expect, may or may not be fatal | ||
addEventListener(listenerErrorIndicator, internalEE, 'error', function(err) { | ||
debug('Error ' + JSON.stringify(err)); | ||
callback(err); | ||
}); | ||
addEventListener(listenerErrorIndicator, internalEE, 'finished', function(obj) { | ||
function emitFinished() { | ||
debug('Backup complete - written ' + JSON.stringify(obj)); | ||
const summary = {total: obj.total}; | ||
ee.emit('finished', summary); | ||
if (callback) callback(null, summary); | ||
// If resuming write a newline as it's possible one would be missing from | ||
// an interruption of the previous backup. If the backup was clean this | ||
// will cause an empty line that will be gracefully handled by the restore. | ||
if (opts.resume) { | ||
targetStream.write('\n'); | ||
} | ||
if (targetStream === process.stdout) { | ||
// stdout cannot emit a finish event so use a final write + callback | ||
targetStream.write('', 'utf8', emitFinished); | ||
} else { | ||
// If we're writing to a file, end the writes and register the | ||
// emitFinished function for a callback when the file stream's finish | ||
// event is emitted. | ||
targetStream.end('', 'utf8', emitFinished); | ||
} | ||
// Get the event emitter from the backup process so we can handle events | ||
// before passing them on to the app's event emitter if needed. | ||
const internalEE = backup(backupDB, opts); | ||
addEventListener(listenerErrorIndicator, internalEE, 'changes', function(batch) { | ||
ee.emit('changes', batch); | ||
}); | ||
addEventListener(listenerErrorIndicator, internalEE, 'received', function(obj, q, logCompletedBatch) { | ||
// this may be too verbose to have as well as the "backed up" message | ||
// debug(' received batch', obj.batch, ' docs: ', obj.total, 'Time', obj.time); | ||
// Callback to emit the written event when the content is flushed | ||
function writeFlushed() { | ||
ee.emit('written', {total: obj.total, time: obj.time, batch: obj.batch}); | ||
if (logCompletedBatch) { | ||
logCompletedBatch(obj.batch); | ||
} | ||
debug(' backed up batch', obj.batch, ' docs: ', obj.total, 'Time', obj.time); | ||
} | ||
// Write the received content to the targetStream | ||
const continueWriting = targetStream.write(JSON.stringify(obj.data) + '\n', | ||
'utf8', | ||
writeFlushed); | ||
if (!continueWriting) { | ||
// The buffer was full, pause the queue to stop the writes until we | ||
// get a drain event | ||
if (q && !q.isPaused) { | ||
q.pause(); | ||
targetStream.once('drain', function() { | ||
q.resume(); | ||
}); | ||
} | ||
} | ||
}); | ||
// For errors we expect, may or may not be fatal | ||
addEventListener(listenerErrorIndicator, internalEE, 'error', function(err) { | ||
debug('Error ' + JSON.stringify(err)); | ||
callback(err); | ||
}); | ||
addEventListener(listenerErrorIndicator, internalEE, 'finished', function(obj) { | ||
function emitFinished() { | ||
debug('Backup complete - written ' + JSON.stringify(obj)); | ||
const summary = {total: obj.total}; | ||
ee.emit('finished', summary); | ||
if (callback) callback(null, summary); | ||
} | ||
if (targetStream === process.stdout) { | ||
// stdout cannot emit a finish event so use a final write + callback | ||
targetStream.write('', 'utf8', emitFinished); | ||
} else { | ||
// If we're writing to a file, end the writes and register the | ||
// emitFinished function for a callback when the file stream's finish | ||
// event is emitted. | ||
targetStream.end('', 'utf8', emitFinished); | ||
} | ||
}); | ||
}); | ||
return ee; | ||
@@ -274,37 +316,50 @@ }, | ||
restoreInternal( | ||
targetUrl, | ||
opts, | ||
srcStream, | ||
ee, | ||
function(err, writer) { | ||
if (err) { | ||
callback(err, null); | ||
return; | ||
// Set up the DB client | ||
const restoreDB = request.client(targetUrl, opts); | ||
// Validate the DB exists, before proceeding to restore | ||
proceedIfDbValid(restoreDB, function(err) { | ||
if (err) { | ||
if (err.name === 'DatabaseNotFound') { | ||
err.message = `${err.message} Create the target database before restoring.`; | ||
} | ||
if (writer != null) { | ||
addEventListener(listenerErrorIndicator, writer, 'restored', function(obj) { | ||
debug(' restored ', obj.total); | ||
ee.emit('restored', {documents: obj.documents, total: obj.total}); | ||
}); | ||
addEventListener(listenerErrorIndicator, writer, 'error', function(err) { | ||
debug('Error ' + JSON.stringify(err)); | ||
// Only call destroy if it is available on the stream | ||
if (srcStream.destroy && srcStream.destroy instanceof Function) { | ||
srcStream.destroy(); | ||
} | ||
callback(err); | ||
}); | ||
addEventListener(listenerErrorIndicator, writer, 'finished', function(obj) { | ||
debug('restore complete'); | ||
ee.emit('finished', {total: obj.total}); | ||
callback(null, obj); | ||
}); | ||
} | ||
// Didn't exist, or another fatal error, exit | ||
callback(err); | ||
return; | ||
} | ||
); | ||
restoreInternal( | ||
restoreDB, | ||
opts, | ||
srcStream, | ||
ee, | ||
function(err, writer) { | ||
if (err) { | ||
callback(err, null); | ||
return; | ||
} | ||
if (writer != null) { | ||
addEventListener(listenerErrorIndicator, writer, 'restored', function(obj) { | ||
debug(' restored ', obj.total); | ||
ee.emit('restored', {documents: obj.documents, total: obj.total}); | ||
}); | ||
addEventListener(listenerErrorIndicator, writer, 'error', function(err) { | ||
debug('Error ' + JSON.stringify(err)); | ||
// Only call destroy if it is available on the stream | ||
if (srcStream.destroy && srcStream.destroy instanceof Function) { | ||
srcStream.destroy(); | ||
} | ||
callback(err); | ||
}); | ||
addEventListener(listenerErrorIndicator, writer, 'finished', function(obj) { | ||
debug('restore complete'); | ||
ee.emit('finished', {total: obj.total}); | ||
callback(null, obj); | ||
}); | ||
} | ||
} | ||
); | ||
}); | ||
return ee; | ||
} | ||
}; | ||
@@ -311,0 +366,0 @@ |
# 2.3.0 (UNRELEASED) | ||
- [NEW] Check for database existence before starting backup. This provides for | ||
better error messages for existence, authentication, and `_bulk_get` problems. | ||
- [FIXED] Intermittent issues with multiple callbacks, particularly noticeable | ||
@@ -6,3 +9,3 @@ when using Node.js 10. | ||
fatal error. | ||
- [UPGRADED] Increased nodejs-cloudant dependency minimum to 2.2.x. | ||
- [UPGRADED] Increased nodejs-cloudant dependency minimum to 2.2.x. | ||
@@ -9,0 +12,0 @@ # 2.2.0 (2018-03-06) |
@@ -18,3 +18,2 @@ // Copyright © 2017, 2018 IBM Corp. All rights reserved. | ||
const events = require('events'); | ||
const request = require('./request.js'); | ||
const fs = require('fs'); | ||
@@ -29,3 +28,3 @@ const error = require('./error.js'); | ||
* | ||
* @param {string} dbUrl - URL of source database. | ||
* @param {string} db - `@cloudant/cloudant` DB object for source database. | ||
* @param {number} blocksize - number of documents to download in single request | ||
@@ -40,3 +39,3 @@ * @param {number} parallelism - number of concurrent downloads | ||
*/ | ||
module.exports = function(dbUrl, options) { | ||
module.exports = function(db, options) { | ||
const ee = new events.EventEmitter(); | ||
@@ -46,4 +45,2 @@ const start = new Date().getTime(); // backup start time | ||
const db = request.client(dbUrl, options); | ||
function proceedWithBackup() { | ||
@@ -50,0 +47,0 @@ if (options.resume) { |
@@ -20,3 +20,3 @@ // Copyright © 2017, 2018 IBM Corp. All rights reserved. | ||
'InvalidOption': 2, | ||
'RestoreDatabaseNotFound': 10, | ||
'DatabaseNotFound': 10, | ||
'Unauthorized': 11, | ||
@@ -23,0 +23,0 @@ 'Forbidden': 12, |
@@ -16,53 +16,17 @@ // Copyright © 2017, 2018 IBM Corp. All rights reserved. | ||
const request = require('./request.js'); | ||
const error = require('./error.js'); | ||
module.exports = function(db, options, readstream, ee, callback) { | ||
var liner = require('../includes/liner.js')(); | ||
var writer = require('../includes/writer.js')(db, options.bufferSize, options.parallelism, ee); | ||
module.exports = function(dbUrl, options, readstream, ee, callback) { | ||
var db = request.client(dbUrl, options); | ||
// pipe the input to the output, via transformation functions | ||
readstream | ||
.pipe(liner) // transform the input stream into per-line | ||
.on('error', function(err) { | ||
// Forward the error to the writer event emitter where we already have | ||
// listeners on for handling errors | ||
writer.emit('error', err); | ||
}) | ||
.pipe(writer); // transform the data | ||
exists(db, function(err) { | ||
if (err) { | ||
callback(err); | ||
return; | ||
} | ||
var liner = require('../includes/liner.js')(); | ||
var writer = require('../includes/writer.js')(db, options.bufferSize, options.parallelism, ee); | ||
// pipe the input to the output, via transformation functions | ||
readstream | ||
.pipe(liner) // transform the input stream into per-line | ||
.on('error', function(err) { | ||
// Forward the error to the writer event emitter where we already have | ||
// listeners on for handling errors | ||
writer.emit('error', err); | ||
}) | ||
.pipe(writer); // transform the data | ||
callback(null, writer); | ||
}); | ||
callback(null, writer); | ||
}; | ||
/* | ||
Check couchDbUrl is a valid database URL. | ||
@param {string} couchDbUrl - Database URL | ||
@param {function(err, exists)} callback - exists is true if database exists | ||
*/ | ||
function exists(db, callback) { | ||
db.head('', function(err) { | ||
err = error.convertResponseError(err, function(err) { | ||
if (err && err.statusCode === 404) { | ||
// Override the error type and mesasge for the DB not found case | ||
var noDBErr = new Error(`Database ${db.config.url}/${db.config.db} does not exist. ` + | ||
'Create the target database before restoring.'); | ||
noDBErr.name = 'RestoreDatabaseNotFound'; | ||
return noDBErr; | ||
} else { | ||
// Delegate to the default error factory if it wasn't a 404 | ||
return error.convertResponseError(err); | ||
} | ||
}); | ||
// Callback with or without (i.e. undefined) error | ||
callback(err); | ||
}); | ||
} |
@@ -19,7 +19,4 @@ // Copyright © 2017, 2018 IBM Corp. All rights reserved. | ||
const events = require('events'); | ||
const request = require('./request.js'); | ||
module.exports = function(dbUrl, options) { | ||
var db = request.client(dbUrl, options); | ||
module.exports = function(db, options) { | ||
const ee = new events.EventEmitter(); | ||
@@ -26,0 +23,0 @@ const start = new Date().getTime(); |
{ | ||
"name": "@cloudant/couchbackup", | ||
"version": "2.3.0-SNAPSHOT.127", | ||
"version": "2.3.0-SNAPSHOT.128", | ||
"description": "CouchBackup - command-line backup utility for Cloudant/CouchDB", | ||
@@ -5,0 +5,0 @@ "homepage": "https://github.com/cloudant/couchbackup", |
@@ -382,2 +382,3 @@ # CouchBackup | ||
* `2`: invalid CLI option. | ||
* `10`: backup source or restore target database does not exist. | ||
* `11`: unauthorized credentials for the database. | ||
@@ -397,4 +398,2 @@ * `12`: incorrect permissions for the database. | ||
* `10`: restore target database does not exist. | ||
## Note on attachments | ||
@@ -401,0 +400,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
93170
1655
409