Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

couch-continuum

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

couch-continuum - npm Package Compare versions

Comparing version 2.2.3 to 2.2.4

261

bin.js

@@ -54,14 +54,61 @@ #!/usr/bin/env node

function catchError (error) {
console.log('ERROR')
if (error.error === 'not_found') {
console.log('Primary database does not exist. There is nothing to migrate.')
} else if (error.error === 'unauthorized') {
console.log('Could not authenticate with CouchDB. Are the credentials correct?')
} else if (error.code === 'EACCES') {
console.log('Could not access the checkpoint document. Are you running as a different user?')
} else {
console.log('Unexpected error: %j', error)
}
process.exit(1)
function generalOptions (yargs) {
return yargs
// backwards compat with old flag names
.alias('source', 'dbNames')
.alias('source', 'N')
.alias('target', 'copyName')
.alias('target', 'c')
// actual options
.options({
source: {
alias: 's',
description: 'The name or URL of a database to use as a primary.',
required: true,
type: 'string'
},
target: {
alias: 't',
description: 'The name or URL of a database to use as a replica. Defaults to {source}_temp_copy',
type: 'string'
},
couchUrl: {
alias: 'u',
description: 'The URL of the CouchDB cluster to act upon.',
default: process.env.COUCH_URL || 'http://localhost:5984'
},
interval: {
alias: 'i',
description: 'How often (in milliseconds) to check replication tasks for progress.',
default: 1000
},
q: {
description: 'The desired "q" value for the new database.',
type: 'number'
},
n: {
description: 'The desired "n" value for the new database.',
type: 'number'
},
verbose: {
alias: 'v',
description: 'Enable verbose logging.',
type: 'boolean'
},
placement: {
alias: 'p',
description: 'Placement rule for the affected database(s).',
type: 'string'
},
filterTombstones: {
alias: 'f',
description: 'Filter tombstones during replica creation. Does not work with CouchDB 1.x',
default: false
},
replicateSecurity: {
alias: 'r',
description: 'Replicate a database\'s /_security object in addition to its documents.',
default: true
}
})
}

@@ -78,12 +125,17 @@

description: 'Migrate a database to new settings.',
builder: generalOptions,
handler: async function (argv) {
const continuum = getContinuum(argv)
log(`Migrating database: ${continuum.source.host}${continuum.source.path}`)
try {
await continuum.createReplica()
const consent = await getConsent()
if (!consent) return log('Could not acquire consent. Exiting...')
await continuum.replacePrimary()
console.log(`Migrated database: ${continuum.source.host}${continuum.source.path}`)
} catch (error) { catchError(error) }
log(`Migrating database: ${continuum.source.host}${continuum.source.pathname}`)
await continuum.createReplica()
const consent1 = await getConsent()
if (!consent1) return log('Could not acquire consent. Exiting...')
await continuum.replacePrimary()
console.log(`Migrated database: ${continuum.source.host}${continuum.source.pathname}`)
log(`Migrating database: ${continuum.source.host}${continuum.source.pathname}`)
await continuum.createReplica()
const consent2 = await getConsent()
if (!consent2) return log('Could not acquire consent. Exiting...')
await continuum.replacePrimary()
console.log(`Migrated database: ${continuum.source.host}${continuum.source.pathname}`)
}

@@ -95,9 +147,11 @@ })

description: 'Create a replica of the given primary.',
builder: generalOptions,
handler: async function (argv) {
const continuum = getContinuum(argv)
log(`Creating replica of ${continuum.source.host}${continuum.source.path} at ${continuum.target.host}${continuum.target.path}`)
try {
await continuum.createReplica()
console.log(`Created replica of ${continuum.source.host}${continuum.source.path}`)
} catch (error) { catchError(error) }
log(`Creating replica of ${continuum.source.host}${continuum.source.pathname} at ${continuum.target.host}${continuum.target.pathname}`)
await continuum.createReplica()
console.log(`Created replica of ${continuum.source.host}${continuum.source.pathname}`)
log(`Creating replica of ${continuum.source.host}${continuum.source.pathname} at ${continuum.target.host}${continuum.target.pathname}`)
await continuum.createReplica()
console.log(`Created replica of ${continuum.source.host}${continuum.source.pathname}`)
}

@@ -109,11 +163,10 @@ })

description: 'Replace the given primary with the indicated replica.',
builder: generalOptions,
handler: async function (argv) {
const continuum = getContinuum(argv)
log(`Replacing primary ${continuum.source.host}${continuum.source.path} with ${continuum.target.host}${continuum.target.path}`)
try {
const consent = await getConsent()
if (!consent) return log('Could not acquire consent. Exiting...')
await continuum.replacePrimary()
console.log(`Successfully replaced ${continuum.source.host}${continuum.source.path}`)
} catch (error) { catchError(error) }
log(`Replacing primary ${continuum.source.host}${continuum.source.pathname} with ${continuum.target.host}${continuum.target.pathname}`)
const consent = await getConsent()
if (!consent) return log('Could not acquire consent. Exiting...')
await continuum.replacePrimary()
console.log(`Successfully replaced ${continuum.source.host}${continuum.source.pathname}`)
}

@@ -125,80 +178,82 @@ })

description: 'Migrate all non-special databases to new settings.',
builder: function (yargs) {
yargs.options({
couchUrl: {
alias: 'u',
description: 'The URL of the CouchDB cluster to act upon.',
default: process.env.COUCH_URL || 'http://localhost:5984'
},
interval: {
alias: 'i',
description: 'How often (in milliseconds) to check replication tasks for progress.',
default: 1000
},
q: {
description: 'The desired "q" value for the new database.',
type: 'number'
},
n: {
description: 'The desired "n" value for the new database.',
type: 'number'
},
verbose: {
alias: 'v',
description: 'Enable verbose logging.',
type: 'boolean'
},
placement: {
alias: 'p',
description: 'Placement rule for the affected database(s).',
type: 'string'
},
filterTombstones: {
alias: 'f',
description: 'Filter tombstones during replica creation. Does not work with CouchDB 1.x',
default: false
},
replicateSecurity: {
alias: 'r',
description: 'Replicate a database\'s /_security object in addition to its documents.',
default: true
}
})
},
handler: async function (argv) {
const { couchUrl, verbose } = argv
if (verbose) { process.env.LOG = true }
try {
const dbNames = await CouchContinuum.getRemaining(couchUrl)
const continuums = dbNames.map((dbName) => {
return new CouchContinuum({ dbName, ...argv })
})
log('Creating replicas...')
await CouchContinuum.createReplicas(continuums)
const consent = await getConsent('Ready to replace primaries with replicas. Continue? [y/N] ')
if (!consent) return console.log('Could not acquire consent. Exiting...')
log('Replacing primaries...')
await CouchContinuum.replacePrimaries(continuums)
await CouchContinuum.removeCheckpoint()
console.log(`Successfully migrated databases: ${dbNames.join(', ')}`)
} catch (error) { catchError(error) }
const dbNames = await CouchContinuum.getRemaining(couchUrl)
if (!dbNames.length) {
console.log('No eligible databases to migrate.')
return
}
const continuums = dbNames.map((source) => {
return new CouchContinuum({ source, ...argv })
})
log('Creating replicas...')
await CouchContinuum.createReplicas(continuums)
const consent = await getConsent('Ready to replace primaries with replicas. Continue? [y/N] ')
if (!consent) return console.log('Could not acquire consent. Exiting...')
log('Replacing primaries...')
await CouchContinuum.replacePrimaries(continuums)
await CouchContinuum.removeCheckpoint()
console.log(`Successfully migrated databases: ${dbNames.join(', ')}`)
}
})
// backwards compat with old flag names
.alias('source', 'dbNames')
.alias('source', 'N')
.alias('target', 'copyName')
.alias('target', 'c')
// actual options
.options({
source: {
alias: 's',
description: 'The name or URL of a database to use as a primary.',
required: true,
type: 'string'
},
target: {
alias: 't',
description: 'The name or URL of a database to use as a replica. Defaults to {source}_temp_copy',
type: 'string'
},
couchUrl: {
alias: 'u',
description: 'The URL of the CouchDB cluster to act upon.',
default: process.env.COUCH_URL || 'http://localhost:5984'
},
interval: {
alias: 'i',
description: 'How often (in milliseconds) to check replication tasks for progress.',
default: 1000
},
q: {
description: 'The desired "q" value for the new database.',
type: 'number'
},
n: {
description: 'The desired "n" value for the new database.',
type: 'number'
},
verbose: {
alias: 'v',
description: 'Enable verbose logging.',
type: 'boolean'
},
placement: {
alias: 'p',
description: 'Placement rule for the affected database(s).',
type: 'string'
},
filterTombstones: {
alias: 'f',
description: 'Filter tombstones during replica creation. Does not work with CouchDB 1.x',
default: false
},
replicateSecurity: {
alias: 'r',
description: 'Replicate a database\'s /_security object in addition to its documents.',
default: true
.config()
.alias('h', 'help')
.fail((msg, error, yargs) => {
if (!error) {
console.log(msg)
} else if (error.error === 'not_found') {
console.log('Primary database does not exist. There is nothing to migrate.')
} else if (error.error === 'unauthorized') {
console.log('Could not authenticate with CouchDB. Are the credentials correct?')
} else if (error.code === 'EACCES') {
console.log('Could not access the checkpoint document. Are you running as a different user?')
} else {
console.log('Unexpected error. Please report this so we can fix it!')
console.log(error)
}
process.exit(1)
})
.config()
.alias('h', 'help')
.parse()

@@ -1,2 +0,2 @@

const assert = require('assert')
const assert = require('assert').strict
const path = require('path')

@@ -59,3 +59,10 @@ const ProgressBar = require('progress')

static async removeCheckpoint () {
await unlink(checkpoint)
try {
await unlink(checkpoint)
} catch (error) {
// don't complain if checkpoint is already missing
if (error.code !== 'ENOENT') {
throw error
}
}
}

@@ -86,15 +93,32 @@

static async _isAvailable (dbUrl) {
const { down } = await request({
url: `${dbUrl}/_local/in-maintenance`,
json: true
})
return !down
try {
const { down } = await request({
url: `${dbUrl}/_local/in-maintenance`,
json: true
})
return !down
} catch (error) {
if (error.error !== 'not_found') {
throw error
} else {
// document doesn't exist, so, db must be available
return true
}
}
}
static async _setUnavailable (dbUrl) {
await request({
url: `${dbUrl}/_local/in-maintenance`,
method: 'PUT',
json: { down: true }
})
const url = `${dbUrl}/_local/in-maintenance`
try {
// update
const { _rev: rev } = await request({ url, json: true })
return request({ url, json: { _rev: rev, down: true }, method: 'PUT' })
} catch (error) {
if (error.error === 'not_found') {
// create
await request({ url, method: 'PUT', json: { down: true } })
} else {
throw error
}
}
}

@@ -104,4 +128,13 @@

const url = `${dbUrl}/_local/in-maintenance`
const { _rev: rev } = await request({ url, json: true })
return request({ url, qs: { rev }, method: 'DELETE' })
try {
const { _rev: rev } = await request({ url, json: true })
return request({ url, qs: { rev }, method: 'DELETE' })
} catch (error) {
if (error.error === 'not_found') {
// already available, nothing to do
return null
} else {
throw error
}
}
}

@@ -172,8 +205,15 @@

if (this.placement) { qs.placement = this.placement }
return request({
url: dbUrl,
method: 'PUT',
qs,
json: true
})
try {
const result = await request({
url: dbUrl,
method: 'PUT',
qs,
json: true
})
return result
} catch (error) {
if (error.error !== 'file_exists') {
throw error
}
}
}

@@ -264,14 +304,28 @@

// TODO check all known hosts
const activeTasks = await request({
url: `${this.url.href}_active_tasks`,
json: true
})
const { jobs } = await request({
url: `${this.url.href}_scheduler/jobs`,
json: true
}).then(({ jobs }) => {
return { jobs: jobs || [] }
})
for (const { database } of [...jobs, ...activeTasks]) {
assert.notStrictEqual(database, dbName, `${dbName} is still in use.`)
try {
const activeTasks = await request({
url: `${this.url.href}_active_tasks`,
json: true
})
const { jobs } = await request({
url: `${this.url.href}_scheduler/jobs`,
json: true
}).then(({ jobs }) => {
return { jobs: jobs || [] }
})
for (const { database, source, target } of [...jobs, ...activeTasks]) {
const re = new RegExp(`/${dbName}/`)
if (database) {
assert.strictEqual(re.test(database), true)
}
assert.strictEqual(re.test(source), true)
assert.strictEqual(re.test(target), true)
}
} catch (error) {
if (error.error === 'illegal_database_name') {
// 1.x -- this block is just for travis' test conditions
return null
} else {
throw error
}
}

@@ -306,3 +360,3 @@ }

async replacePrimary () {
log(`Replacing primary ${this.source.host}${this.source.pathname} using ${this.target.host}${this.target.path}...`)
log(`Replacing primary ${this.source.host}${this.source.pathname} using ${this.target.host}${this.target.pathname}...`)
log('[0/8] Checking if primary is in use...')

@@ -309,0 +363,0 @@ await this._isInUse(this.source.pathname.slice(1))

@@ -7,5 +7,21 @@ const request = require('request')

if (err) return reject(err)
return resolve(body)
if (res.statusCode >= 400) {
let string, json
if (typeof body === 'string') {
string = body
json = { options, ...JSON.parse(body) }
} else {
string = JSON.stringify(body)
json = { options, ...body }
}
const error = new Error(string)
Object.entries(json).map(([prop, value]) => {
error[prop] = value
})
return reject(error)
} else {
return resolve(body)
}
})
})
}
{
"name": "couch-continuum",
"version": "2.2.3",
"version": "2.2.4",
"description": "Tool for migrating CouchDB databases to new configuration values.",

@@ -10,3 +10,3 @@ "main": "index.js",

"scripts": {
"test": "standard && dependency-check . --unused --no-dev && mocha && npm audit"
"test": "standard && dependency-check . --unused --no-dev && LOG=true mocha && npm audit"
},

@@ -17,3 +17,3 @@ "author": "Diana Thayer <garbados@gmail.com>",

"progress": "^2.0.3",
"request": "^2.88.0",
"request": "^2.88.2",
"yargs": "^15.3.1"

@@ -23,4 +23,4 @@ },

"dependency-check": "^4.1.0",
"mocha": "^7.1.1",
"standard": "^14.3.3"
"mocha": "^8.0.1",
"standard": "^14.3.4"
},

@@ -27,0 +27,0 @@ "repository": {

@@ -169,2 +169,2 @@ # couch-continuum

(c) 2018 Neighbourhoodie Software & Open Source contributors
(c) 2018–2020 Neighbourhoodie Software & Open Source contributors

@@ -1,4 +0,4 @@

/* globals describe, it, beforeEach, before, afterEach */
/* globals describe, it, beforeEach, before, afterEach, after */
const assert = require('assert')
const assert = require('assert').strict
const CouchContinuum = require('..')

@@ -21,4 +21,10 @@ const request = require('../lib/request')

// ensure db exists
const url = [couchUrl, dbName].join('/')
await request({ url, method: 'PUT' })
const url = `${couchUrl}/${dbName}`
try {
await request({ url, method: 'PUT' })
} catch (error) {
if (error.error !== 'file_exists') {
throw error
}
}
await request({

@@ -37,4 +43,14 @@ url: [url, '_bulk_docs'].join('/'),

// destroy dbs
await request({ url: `${couchUrl}/${dbName}`, method: 'DELETE' })
await request({ url: `${couchUrl}/temp_copy_${dbName}`, method: 'DELETE' })
const urls = ['', 'temp_copy_'].map((s) => {
return `${couchUrl}/${s}${dbName}`
})
for (const url of urls) {
try {
await request({ url, method: 'DELETE' })
} catch (error) {
if (error.error !== 'not_found') {
throw error
}
}
}
})

@@ -70,4 +86,7 @@

// verify cleanup
const { error } = await request({ url: `${couchUrl}/temp_copy_${dbName}`, json: true })
assert.strictEqual(error, 'not_found')
try {
await request({ url: `${couchUrl}/temp_copy_${dbName}`, json: true })
} catch (error) {
assert.strictEqual(error.error, 'not_found')
}
})

@@ -188,2 +207,44 @@

})
it('should run timer code', async function () {
const interval = 1
const continuum = new CouchContinuum({
couchUrl,
source: dbName,
interval
})
await continuum.createReplica()
})
it('should handle removing a checkpoint more than once', async function () {
const checkpoint = '.testcheckpoint'
await CouchContinuum.makeCheckpoint(checkpoint)
await CouchContinuum.removeCheckpoint(checkpoint)
await CouchContinuum.removeCheckpoint(checkpoint)
})
describe('_isInUse', function () {
before(async function () {
this.continuum = new CouchContinuum({ couchUrl, source: dbName })
await this.continuum._createDb(this.continuum.source.href)
await this.continuum._createDb(this.continuum.target.href)
await request({
method: 'POST',
url: `${this.continuum.url.href}_replicate`,
json: {
continuous: true,
source: this.continuum.source.href,
target: this.continuum.target.href
}
})
})
it('should check for databases in use', function () {
assert.rejects(this.continuum._isInUse(dbName))
})
after(async function () {
await this.promise
})
})
})

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc