2015-07-20 00:09:47 -07:00
'use strict' ;
exports = module . exports = {
get : get ,
getByHttpPort : getByHttpPort ,
2015-09-14 17:01:04 -07:00
getByContainerId : getByContainerId ,
2015-07-20 00:09:47 -07:00
add : add ,
exists : exists ,
del : del ,
update : update ,
getAll : getAll ,
getPortBindings : getPortBindings ,
setAddonConfig : setAddonConfig ,
getAddonConfig : getAddonConfig ,
getAddonConfigByAppId : getAddonConfigByAppId ,
unsetAddonConfig : unsetAddonConfig ,
unsetAddonConfigByAppId : unsetAddonConfigByAppId ,
setHealth : setHealth ,
setInstallationCommand : setInstallationCommand ,
setRunCommand : setRunCommand ,
getAppStoreIds : getAppStoreIds ,
// installation codes (keep in sync in UI)
ISTATE _PENDING _INSTALL : 'pending_install' , // installs and fresh reinstalls
2016-06-17 16:56:15 -05:00
ISTATE _PENDING _CLONE : 'pending_clone' , // clone
2015-07-20 00:09:47 -07:00
ISTATE _PENDING _CONFIGURE : 'pending_configure' , // config (location, port) changes and on infra update
ISTATE _PENDING _UNINSTALL : 'pending_uninstall' , // uninstallation
ISTATE _PENDING _RESTORE : 'pending_restore' , // restore to previous backup or on upgrade
ISTATE _PENDING _UPDATE : 'pending_update' , // update from installed state preserving data
ISTATE _PENDING _FORCE _UPDATE : 'pending_force_update' , // update from any state preserving data
ISTATE _PENDING _BACKUP : 'pending_backup' , // backup the app
ISTATE _ERROR : 'error' , // error executing last pending_* command
ISTATE _INSTALLED : 'installed' , // app is installed
RSTATE _RUNNING : 'running' ,
RSTATE _PENDING _START : 'pending_start' ,
RSTATE _PENDING _STOP : 'pending_stop' ,
RSTATE _STOPPED : 'stopped' , // app stopped by use
2015-08-24 21:58:04 -07:00
// run codes (keep in sync in UI)
2015-07-20 00:09:47 -07:00
HEALTH _HEALTHY : 'healthy' ,
HEALTH _UNHEALTHY : 'unhealthy' ,
HEALTH _ERROR : 'error' ,
HEALTH _DEAD : 'dead' ,
_clear : clear
} ;
var assert = require ( 'assert' ) ,
async = require ( 'async' ) ,
database = require ( './database.js' ) ,
DatabaseError = require ( './databaseerror' ) ,
2017-02-08 23:50:26 +01:00
mailboxdb = require ( './mailboxdb.js' ) ,
2015-07-20 00:09:47 -07:00
safe = require ( 'safetydance' ) ,
util = require ( 'util' ) ;
var APPS _FIELDS _PREFIXED = [ 'apps.id' , 'apps.appStoreId' , 'apps.installationState' , 'apps.installationProgress' , 'apps.runState' ,
'apps.health' , 'apps.containerId' , 'apps.manifestJson' , 'apps.httpPort' , 'apps.location' , 'apps.dnsRecordId' ,
2016-09-07 00:31:25 -07:00
'apps.accessRestrictionJson' , 'apps.lastBackupId' , 'apps.oldConfigJson' , 'apps.memoryLimit' , 'apps.altDomain' ,
2017-01-20 05:48:25 -08:00
'apps.xFrameOptions' , 'apps.sso' , 'apps.debugModeJson' ] . join ( ',' ) ;
2015-07-20 00:09:47 -07:00
var PORT _BINDINGS _FIELDS = [ 'hostPort' , 'environmentVariable' , 'appId' ] . join ( ',' ) ;
function postProcess ( result ) {
assert . strictEqual ( typeof result , 'object' ) ;
assert ( result . manifestJson === null || typeof result . manifestJson === 'string' ) ;
result . manifest = safe . JSON . parse ( result . manifestJson ) ;
delete result . manifestJson ;
assert ( result . oldConfigJson === null || typeof result . oldConfigJson === 'string' ) ;
result . oldConfig = safe . JSON . parse ( result . oldConfigJson ) ;
delete result . oldConfigJson ;
assert ( result . hostPorts === null || typeof result . hostPorts === 'string' ) ;
assert ( result . environmentVariables === null || typeof result . environmentVariables === 'string' ) ;
result . portBindings = { } ;
var hostPorts = result . hostPorts === null ? [ ] : result . hostPorts . split ( ',' ) ;
var environmentVariables = result . environmentVariables === null ? [ ] : result . environmentVariables . split ( ',' ) ;
delete result . hostPorts ;
delete result . environmentVariables ;
for ( var i = 0 ; i < environmentVariables . length ; i ++ ) {
result . portBindings [ environmentVariables [ i ] ] = parseInt ( hostPorts [ i ] , 10 ) ;
}
2015-10-13 12:29:40 +02:00
2015-10-16 15:11:54 +02:00
assert ( result . accessRestrictionJson === null || typeof result . accessRestrictionJson === 'string' ) ;
result . accessRestriction = safe . JSON . parse ( result . accessRestrictionJson ) ;
if ( result . accessRestriction && ! result . accessRestriction . users ) result . accessRestriction . users = [ ] ;
delete result . accessRestrictionJson ;
2016-07-15 11:08:11 +02:00
// TODO remove later once all apps have this attribute
result . xFrameOptions = result . xFrameOptions || 'SAMEORIGIN' ;
2016-09-06 21:56:36 -07:00
2016-11-11 10:53:41 +05:30
result . sso = ! ! result . sso ; // make it bool
2017-01-20 05:48:25 -08:00
assert ( result . debugModeJson === null || typeof result . debugModeJson === 'string' ) ;
result . debugMode = safe . JSON . parse ( result . debugModeJson ) ;
delete result . debugModeJson ;
2015-07-20 00:09:47 -07:00
}
function get ( id , callback ) {
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
database . query ( 'SELECT ' + APPS _FIELDS _PREFIXED + ','
+ 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables'
+ ' FROM apps LEFT OUTER JOIN appPortBindings ON apps.id = appPortBindings.appId WHERE apps.id = ? GROUP BY apps.id' , [ id ] , function ( error , result ) {
if ( error ) return callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , error ) ) ;
if ( result . length === 0 ) return callback ( new DatabaseError ( DatabaseError . NOT _FOUND ) ) ;
postProcess ( result [ 0 ] ) ;
callback ( null , result [ 0 ] ) ;
} ) ;
}
function getByHttpPort ( httpPort , callback ) {
assert . strictEqual ( typeof httpPort , 'number' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
database . query ( 'SELECT ' + APPS _FIELDS _PREFIXED + ','
+ 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables'
+ ' FROM apps LEFT OUTER JOIN appPortBindings ON apps.id = appPortBindings.appId WHERE httpPort = ? GROUP BY apps.id' , [ httpPort ] , function ( error , result ) {
if ( error ) return callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , error ) ) ;
if ( result . length === 0 ) return callback ( new DatabaseError ( DatabaseError . NOT _FOUND ) ) ;
postProcess ( result [ 0 ] ) ;
callback ( null , result [ 0 ] ) ;
} ) ;
}
2015-09-14 17:01:04 -07:00
function getByContainerId ( containerId , callback ) {
assert . strictEqual ( typeof containerId , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
database . query ( 'SELECT ' + APPS _FIELDS _PREFIXED + ','
+ 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables'
+ ' FROM apps LEFT OUTER JOIN appPortBindings ON apps.id = appPortBindings.appId WHERE containerId = ? GROUP BY apps.id' , [ containerId ] , function ( error , result ) {
if ( error ) return callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , error ) ) ;
if ( result . length === 0 ) return callback ( new DatabaseError ( DatabaseError . NOT _FOUND ) ) ;
postProcess ( result [ 0 ] ) ;
callback ( null , result [ 0 ] ) ;
} ) ;
}
2015-07-20 00:09:47 -07:00
function getAll ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
database . query ( 'SELECT ' + APPS _FIELDS _PREFIXED + ','
+ 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables'
+ ' FROM apps LEFT OUTER JOIN appPortBindings ON apps.id = appPortBindings.appId'
+ ' GROUP BY apps.id ORDER BY apps.id' , function ( error , results ) {
if ( error ) return callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , error ) ) ;
results . forEach ( postProcess ) ;
callback ( null , results ) ;
} ) ;
}
2016-06-17 16:43:35 -05:00
function add ( id , appStoreId , manifest , location , portBindings , data , callback ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof appStoreId , 'string' ) ;
assert ( manifest && typeof manifest === 'object' ) ;
assert . strictEqual ( typeof manifest . version , 'string' ) ;
assert . strictEqual ( typeof location , 'string' ) ;
assert . strictEqual ( typeof portBindings , 'object' ) ;
2016-06-17 16:43:35 -05:00
assert ( data && typeof data === 'object' ) ;
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
portBindings = portBindings || { } ;
var manifestJson = JSON . stringify ( manifest ) ;
2016-06-17 16:43:35 -05:00
var accessRestriction = data . accessRestriction || null ;
2016-06-17 16:56:15 -05:00
var accessRestrictionJson = JSON . stringify ( accessRestriction ) ;
2016-06-17 16:43:35 -05:00
var memoryLimit = data . memoryLimit || 0 ;
var altDomain = data . altDomain || null ;
2016-07-14 15:47:20 +02:00
var xFrameOptions = data . xFrameOptions || '' ;
2016-06-17 16:56:15 -05:00
var installationState = data . installationState || exports . ISTATE _PENDING _INSTALL ;
2016-06-17 17:53:27 -05:00
var lastBackupId = data . lastBackupId || null ; // used when cloning
2016-11-19 21:59:06 +05:30
var sso = 'sso' in data ? data . sso : null ;
2017-01-20 05:48:25 -08:00
var debugModeJson = data . debugMode ? JSON . stringify ( data . debugMode ) : null ;
2015-07-20 00:09:47 -07:00
2017-02-08 23:50:26 +01:00
var queries = [ ] ;
2015-07-20 00:09:47 -07:00
queries . push ( {
2017-01-20 05:48:25 -08:00
query : 'INSERT INTO apps (id, appStoreId, manifestJson, installationState, location, accessRestrictionJson, memoryLimit, altDomain, xFrameOptions, lastBackupId, sso, debugModeJson) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' ,
args : [ id , appStoreId , manifestJson , installationState , location , accessRestrictionJson , memoryLimit , altDomain , xFrameOptions , lastBackupId , sso , debugModeJson ]
2015-07-20 00:09:47 -07:00
} ) ;
Object . keys ( portBindings ) . forEach ( function ( env ) {
queries . push ( {
query : 'INSERT INTO appPortBindings (environmentVariable, hostPort, appId) VALUES (?, ?, ?)' ,
args : [ env , portBindings [ env ] , id ]
} ) ;
} ) ;
2017-02-08 23:50:26 +01:00
// only allocate a mailbox if fromEmail is set
if ( data . fromEmail ) {
queries . push ( {
query : 'INSERT INTO mailboxes (name, ownerId, ownerType) VALUES (?, ?, ?)' ,
args : [ data . fromEmail , id , mailboxdb . TYPE _APP ]
} ) ;
}
2015-07-20 00:09:47 -07:00
database . transaction ( queries , function ( error ) {
if ( error && error . code === 'ER_DUP_ENTRY' ) return callback ( new DatabaseError ( DatabaseError . ALREADY _EXISTS , error . message ) ) ;
if ( error ) return callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , error ) ) ;
callback ( null ) ;
} ) ;
}
function exists ( id , callback ) {
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
database . query ( 'SELECT 1 FROM apps WHERE id=?' , [ id ] , function ( error , result ) {
if ( error ) return callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , error ) ) ;
return callback ( null , result . length !== 0 ) ;
} ) ;
}
function getPortBindings ( id , callback ) {
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
database . query ( 'SELECT ' + PORT _BINDINGS _FIELDS + ' FROM appPortBindings WHERE appId = ?' , [ id ] , function ( error , results ) {
if ( error ) return callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , error ) ) ;
var portBindings = { } ;
for ( var i = 0 ; i < results . length ; i ++ ) {
portBindings [ results [ i ] . environmentVariable ] = results [ i ] . hostPort ;
}
callback ( null , portBindings ) ;
} ) ;
}
function del ( id , callback ) {
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
var queries = [
{ query : 'DELETE FROM appPortBindings WHERE appId = ?' , args : [ id ] } ,
{ query : 'DELETE FROM apps WHERE id = ?' , args : [ id ] }
] ;
database . transaction ( queries , function ( error , results ) {
if ( error ) return callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , error ) ) ;
if ( results [ 1 ] . affectedRows !== 1 ) return callback ( new DatabaseError ( DatabaseError . NOT _FOUND ) ) ;
callback ( null ) ;
} ) ;
}
function clear ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
async . series ( [
database . query . bind ( null , 'DELETE FROM appPortBindings' ) ,
database . query . bind ( null , 'DELETE FROM appAddonConfigs' ) ,
database . query . bind ( null , 'DELETE FROM apps' )
] , function ( error ) {
if ( error ) return callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , error ) ) ;
return callback ( null ) ;
} ) ;
}
function update ( id , app , callback ) {
updateWithConstraints ( id , app , '' , callback ) ;
}
function updateWithConstraints ( id , app , constraints , callback ) {
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof constraints , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
assert ( ! ( 'portBindings' in app ) || typeof app . portBindings === 'object' ) ;
2016-02-18 17:28:00 +01:00
assert ( ! ( 'accessRestriction' in app ) || typeof app . accessRestriction === 'object' || app . accessRestriction === '' ) ;
2015-07-20 00:09:47 -07:00
var queries = [ ] ;
if ( 'portBindings' in app ) {
var portBindings = app . portBindings || { } ;
// replace entries by app id
queries . push ( { query : 'DELETE FROM appPortBindings WHERE appId = ?' , args : [ id ] } ) ;
Object . keys ( portBindings ) . forEach ( function ( env ) {
var values = [ portBindings [ env ] , env , id ] ;
queries . push ( { query : 'INSERT INTO appPortBindings (hostPort, environmentVariable, appId) VALUES(?, ?, ?)' , args : values } ) ;
} ) ;
}
var fields = [ ] , values = [ ] ;
for ( var p in app ) {
if ( p === 'manifest' ) {
fields . push ( 'manifestJson = ?' ) ;
values . push ( JSON . stringify ( app [ p ] ) ) ;
} else if ( p === 'oldConfig' ) {
fields . push ( 'oldConfigJson = ?' ) ;
values . push ( JSON . stringify ( app [ p ] ) ) ;
2015-10-16 15:11:54 +02:00
} else if ( p === 'accessRestriction' ) {
fields . push ( 'accessRestrictionJson = ?' ) ;
values . push ( JSON . stringify ( app [ p ] ) ) ;
2017-01-20 09:40:11 -08:00
} else if ( p === 'debugMode' ) {
fields . push ( 'debugModeJson = ?' ) ;
values . push ( JSON . stringify ( app [ p ] ) ) ;
2015-07-20 00:09:47 -07:00
} else if ( p !== 'portBindings' ) {
fields . push ( p + ' = ?' ) ;
values . push ( app [ p ] ) ;
}
}
if ( values . length !== 0 ) {
values . push ( id ) ;
queries . push ( { query : 'UPDATE apps SET ' + fields . join ( ', ' ) + ' WHERE id = ? ' + constraints , args : values } ) ;
}
database . transaction ( queries , function ( error , results ) {
if ( error && error . code === 'ER_DUP_ENTRY' ) return callback ( new DatabaseError ( DatabaseError . ALREADY _EXISTS , error . message ) ) ;
if ( error ) return callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , error ) ) ;
if ( results [ results . length - 1 ] . affectedRows !== 1 ) return callback ( new DatabaseError ( DatabaseError . NOT _FOUND ) ) ;
return callback ( null ) ;
} ) ;
}
// not sure if health should influence runState
function setHealth ( appId , health , callback ) {
assert . strictEqual ( typeof appId , 'string' ) ;
assert . strictEqual ( typeof health , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
var values = { health : health } ;
var constraints = 'AND runState NOT LIKE "pending_%" AND installationState = "installed"' ;
updateWithConstraints ( appId , values , constraints , callback ) ;
}
function setInstallationCommand ( appId , installationState , values , callback ) {
assert . strictEqual ( typeof appId , 'string' ) ;
assert . strictEqual ( typeof installationState , 'string' ) ;
if ( typeof values === 'function' ) {
callback = values ;
values = { } ;
} else {
assert . strictEqual ( typeof values , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
}
values . installationState = installationState ;
values . installationProgress = '' ;
// Rules are:
// uninstall is allowed in any state
2015-08-17 21:30:56 -07:00
// force update is allowed in any state including pending_uninstall! (for better or worse)
2015-07-20 00:09:47 -07:00
// restore is allowed from installed or error state
2016-06-14 13:18:37 -07:00
// configure is allowed in installed state or currently configuring or in error state
// update and backup are allowed only in installed state
2015-07-20 00:09:47 -07:00
if ( installationState === exports . ISTATE _PENDING _UNINSTALL || installationState === exports . ISTATE _PENDING _FORCE _UPDATE ) {
updateWithConstraints ( appId , values , '' , callback ) ;
} else if ( installationState === exports . ISTATE _PENDING _RESTORE ) {
updateWithConstraints ( appId , values , 'AND (installationState = "installed" OR installationState = "error")' , callback ) ;
2016-06-20 09:43:09 -05:00
} else if ( installationState === exports . ISTATE _PENDING _UPDATE || installationState === exports . ISTATE _PENDING _BACKUP ) {
2015-07-20 00:09:47 -07:00
updateWithConstraints ( appId , values , 'AND installationState = "installed"' , callback ) ;
2016-06-14 13:18:37 -07:00
} else if ( installationState === exports . ISTATE _PENDING _CONFIGURE ) {
2016-06-20 09:43:09 -05:00
updateWithConstraints ( appId , values , 'AND (installationState = "installed" OR installationState = "pending_configure" OR installationState = "error")' , callback ) ;
2015-07-20 00:09:47 -07:00
} else {
callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , 'invalid installationState' ) ) ;
}
}
function setRunCommand ( appId , runState , callback ) {
assert . strictEqual ( typeof appId , 'string' ) ;
assert . strictEqual ( typeof runState , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
var values = { runState : runState } ;
updateWithConstraints ( appId , values , 'AND runState NOT LIKE "pending_%" AND installationState = "installed"' , callback ) ;
}
function getAppStoreIds ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
database . query ( 'SELECT id, appStoreId FROM apps' , function ( error , results ) {
if ( error ) return callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , error ) ) ;
callback ( null , results ) ;
} ) ;
}
function setAddonConfig ( appId , addonId , env , callback ) {
assert . strictEqual ( typeof appId , 'string' ) ;
assert . strictEqual ( typeof addonId , 'string' ) ;
assert ( util . isArray ( env ) ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
unsetAddonConfig ( appId , addonId , function ( error ) {
if ( error ) return callback ( error ) ;
if ( env . length === 0 ) return callback ( null ) ;
var query = 'INSERT INTO appAddonConfigs(appId, addonId, value) VALUES ' ;
var args = [ ] , queryArgs = [ ] ;
for ( var i = 0 ; i < env . length ; i ++ ) {
args . push ( appId , addonId , env [ i ] ) ;
queryArgs . push ( '(?, ?, ?)' ) ;
}
database . query ( query + queryArgs . join ( ',' ) , args , function ( error ) {
if ( error ) return callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , error ) ) ;
return callback ( null ) ;
} ) ;
} ) ;
}
function unsetAddonConfig ( appId , addonId , callback ) {
assert . strictEqual ( typeof appId , 'string' ) ;
assert . strictEqual ( typeof addonId , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
database . query ( 'DELETE FROM appAddonConfigs WHERE appId = ? AND addonId = ?' , [ appId , addonId ] , function ( error ) {
if ( error ) return callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , error ) ) ;
callback ( null ) ;
} ) ;
}
function unsetAddonConfigByAppId ( appId , callback ) {
assert . strictEqual ( typeof appId , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
database . query ( 'DELETE FROM appAddonConfigs WHERE appId = ?' , [ appId ] , function ( error ) {
if ( error ) return callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , error ) ) ;
callback ( null ) ;
} ) ;
}
function getAddonConfig ( appId , addonId , callback ) {
assert . strictEqual ( typeof appId , 'string' ) ;
assert . strictEqual ( typeof addonId , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
database . query ( 'SELECT value FROM appAddonConfigs WHERE appId = ? AND addonId = ?' , [ appId , addonId ] , function ( error , results ) {
if ( error ) return callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , error ) ) ;
var config = [ ] ;
results . forEach ( function ( v ) { config . push ( v . value ) ; } ) ;
callback ( null , config ) ;
} ) ;
}
function getAddonConfigByAppId ( appId , callback ) {
assert . strictEqual ( typeof appId , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
database . query ( 'SELECT value FROM appAddonConfigs WHERE appId = ?' , [ appId ] , function ( error , results ) {
if ( error ) return callback ( new DatabaseError ( DatabaseError . INTERNAL _ERROR , error ) ) ;
var config = [ ] ;
results . forEach ( function ( v ) { config . push ( v . value ) ; } ) ;
callback ( null , config ) ;
} ) ;
}