move addon config db code to addonconfigs.js

This commit is contained in:
Girish Ramakrishnan
2021-08-19 21:39:27 -07:00
parent 411cc7daa1
commit c5fff756d1
9 changed files with 300 additions and 339 deletions
+81
View File
@@ -0,0 +1,81 @@
'use strict';
exports = module.exports = {
get,
set,
unset,
getByAppId,
getByName,
unsetByAppId,
getAppIdByValue,
};
const assert = require('assert'),
database = require('./database.js');
async function set(appId, addonId, env) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof addonId, 'string');
assert(Array.isArray(env));
await unset(appId, addonId);
if (env.length === 0) return;
const query = 'INSERT INTO appAddonConfigs(appId, addonId, name, value) VALUES ';
const args = [ ], queryArgs = [ ];
for (let i = 0; i < env.length; i++) {
args.push(appId, addonId, env[i].name, env[i].value);
queryArgs.push('(?, ?, ?, ?)');
}
await database.query(query + queryArgs.join(','), args);
}
async function unset(appId, addonId) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof addonId, 'string');
await database.query('DELETE FROM appAddonConfigs WHERE appId = ? AND addonId = ?', [ appId, addonId ]);
}
async function unsetByAppId(appId) {
assert.strictEqual(typeof appId, 'string');
await database.query('DELETE FROM appAddonConfigs WHERE appId = ?', [ appId ]);
}
async function get(appId, addonId) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof addonId, 'string');
const results = await database.query('SELECT name, value FROM appAddonConfigs WHERE appId = ? AND addonId = ?', [ appId, addonId ]);
return results;
}
async function getByAppId(appId) {
assert.strictEqual(typeof appId, 'string');
const results = await database.query('SELECT name, value FROM appAddonConfigs WHERE appId = ?', [ appId ]);
return results;
}
async function getAppIdByValue(addonId, namePattern, value) {
assert.strictEqual(typeof addonId, 'string');
assert.strictEqual(typeof namePattern, 'string');
assert.strictEqual(typeof value, 'string');
const results = await database.query('SELECT appId FROM appAddonConfigs WHERE addonId = ? AND name LIKE ? AND value = ?', [ addonId, namePattern, value ]);
if (results.length === 0) return null;
return results[0].appId;
}
async function getByName(appId, addonId, namePattern) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof addonId, 'string');
assert.strictEqual(typeof namePattern, 'string');
const results = await database.query('SELECT value FROM appAddonConfigs WHERE appId = ? AND addonId = ? AND name LIKE ?', [ appId, addonId, namePattern ]);
if (results.length === 0) return null;
return results[0].value;
}
-107
View File
@@ -10,13 +10,6 @@ exports = module.exports = {
getPortBindings,
delPortBinding,
setAddonConfig,
getAddonConfig,
getAddonConfigByAppId,
getAddonConfigByName,
unsetAddonConfig,
unsetAddonConfigByAppId,
getAppIdByAddonConfigValue,
getByIpAddress,
getIcons,
@@ -495,103 +488,3 @@ function getAppStoreIds(callback) {
callback(null, results);
});
}
function setAddonConfig(appId, addonId, env, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof addonId, 'string');
assert(Array.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, name, value) VALUES ';
var args = [ ], queryArgs = [ ];
for (var i = 0; i < env.length; i++) {
args.push(appId, addonId, env[i].name, env[i].value);
queryArgs.push('(?, ?, ?, ?)');
}
database.query(query + queryArgs.join(','), args, function (error) {
if (error) return callback(new BoxError(BoxError.DATABASE_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 BoxError(BoxError.DATABASE_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 BoxError(BoxError.DATABASE_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 name, value FROM appAddonConfigs WHERE appId = ? AND addonId = ?', [ appId, addonId ], function (error, results) {
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
callback(null, results);
});
}
function getAddonConfigByAppId(appId, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('SELECT name, value FROM appAddonConfigs WHERE appId = ?', [ appId ], function (error, results) {
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
callback(null, results);
});
}
function getAppIdByAddonConfigValue(addonId, namePattern, value, callback) {
assert.strictEqual(typeof addonId, 'string');
assert.strictEqual(typeof namePattern, 'string');
assert.strictEqual(typeof value, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('SELECT appId FROM appAddonConfigs WHERE addonId = ? AND name LIKE ? AND value = ?', [ addonId, namePattern, value ], function (error, results) {
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
if (results.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'App not found'));
callback(null, results[0].appId);
});
}
function getAddonConfigByName(appId, addonId, namePattern, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof addonId, 'string');
assert.strictEqual(typeof namePattern, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('SELECT value FROM appAddonConfigs WHERE appId = ? AND addonId = ? AND name LIKE ?', [ appId, addonId, namePattern ], function (error, results) {
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
if (results.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'App not found'));
callback(null, results[0].value);
});
}
-1
View File
@@ -15,7 +15,6 @@ const assert = require('assert'),
database = require('./database.js'),
hat = require('./hat.js'),
safe = require('safetydance'),
uuid = require('uuid'),
_ = require('underscore');
+83 -84
View File
@@ -329,103 +329,102 @@ function createSubcontainer(app, name, cmd, options, callback) {
// if required, we can make this a manifest and runtime argument later
if (!isAppContainer) memoryLimit *= 2;
services.getEnvironment(app, function (error, addonEnv) {
getMounts(app, async function (error, mounts) {
if (error) return callback(error);
getMounts(app, function (error, mounts) {
if (error) return callback(error);
const [getEnvError, addonEnv] = await services.getEnvironment(app);
if (getEnvError) return callback(getEnvError);
let containerOptions = {
name: name, // for referencing containers
Tty: isAppContainer,
Image: app.manifest.dockerImage,
Cmd: (isAppContainer && app.debugMode && app.debugMode.cmd) ? app.debugMode.cmd : cmd,
Env: stdEnv.concat(addonEnv).concat(portEnv).concat(appEnv),
ExposedPorts: isAppContainer ? exposedPorts : { },
Volumes: { // see also ReadonlyRootfs
'/tmp': {},
'/run': {}
let containerOptions = {
name: name, // for referencing containers
Tty: isAppContainer,
Image: app.manifest.dockerImage,
Cmd: (isAppContainer && app.debugMode && app.debugMode.cmd) ? app.debugMode.cmd : cmd,
Env: stdEnv.concat(addonEnv).concat(portEnv).concat(appEnv),
ExposedPorts: isAppContainer ? exposedPorts : { },
Volumes: { // see also ReadonlyRootfs
'/tmp': {},
'/run': {}
},
Labels: {
'fqdn': app.fqdn,
'appId': app.id,
'isSubcontainer': String(!isAppContainer),
'isCloudronManaged': String(true)
},
HostConfig: {
Mounts: mounts,
LogConfig: {
Type: 'syslog',
Config: {
'tag': app.id,
'syslog-address': 'udp://127.0.0.1:2514', // see apps.js:validatePortBindings()
'syslog-format': 'rfc5424'
}
},
Labels: {
'fqdn': app.fqdn,
'appId': app.id,
'isSubcontainer': String(!isAppContainer),
'isCloudronManaged': String(true)
Memory: system.getMemoryAllocation(memoryLimit),
MemorySwap: memoryLimit, // Memory + Swap
PortBindings: isAppContainer ? dockerPortBindings : { },
PublishAllPorts: false,
ReadonlyRootfs: app.debugMode ? !!app.debugMode.readonlyRootfs : true,
RestartPolicy: {
'Name': isAppContainer ? 'unless-stopped' : 'no',
'MaximumRetryCount': 0
},
HostConfig: {
Mounts: mounts,
LogConfig: {
Type: 'syslog',
Config: {
'tag': app.id,
'syslog-address': 'udp://127.0.0.1:2514', // see apps.js:validatePortBindings()
'syslog-format': 'rfc5424'
}
},
Memory: system.getMemoryAllocation(memoryLimit),
MemorySwap: memoryLimit, // Memory + Swap
PortBindings: isAppContainer ? dockerPortBindings : { },
PublishAllPorts: false,
ReadonlyRootfs: app.debugMode ? !!app.debugMode.readonlyRootfs : true,
RestartPolicy: {
'Name': isAppContainer ? 'unless-stopped' : 'no',
'MaximumRetryCount': 0
},
CpuShares: app.cpuShares,
VolumesFrom: isAppContainer ? null : [ app.containerId + ':rw' ],
SecurityOpt: [ 'apparmor=docker-cloudron-app' ],
CapAdd: [],
CapDrop: []
CpuShares: app.cpuShares,
VolumesFrom: isAppContainer ? null : [ app.containerId + ':rw' ],
SecurityOpt: [ 'apparmor=docker-cloudron-app' ],
CapAdd: [],
CapDrop: []
}
};
// do no set hostname of containers to location as it might conflict with addons names. for example, an app installed in mail
// location may not reach mail container anymore by DNS. We cannot set hostname to fqdn either as that sets up the dns
// name to look up the internal docker ip. this makes curl from within container fail
// Note that Hostname has no effect on DNS. We have to use the --net-alias for dns.
// Hostname cannot be set with container NetworkMode. Subcontainers run is the network space of the app container
// This is done to prevent lots of up/down events and iptables locking
if (isAppContainer) {
containerOptions.Hostname = app.id;
containerOptions.HostConfig.NetworkMode = 'cloudron'; // user defined bridge network
containerOptions.HostConfig.Dns = ['172.18.0.1']; // use internal dns
containerOptions.HostConfig.DnsSearch = ['.']; // use internal dns
containerOptions.NetworkingConfig = {
EndpointsConfig: {
cloudron: {
IPAMConfig: {
IPv4Address: app.containerIp
},
Aliases: [ name ] // adds hostname entry with container name
}
}
};
} else {
containerOptions.HostConfig.NetworkMode = `container:${app.containerId}`; // scheduler containers must have same IP as app for various addon auth
}
// do no set hostname of containers to location as it might conflict with addons names. for example, an app installed in mail
// location may not reach mail container anymore by DNS. We cannot set hostname to fqdn either as that sets up the dns
// name to look up the internal docker ip. this makes curl from within container fail
// Note that Hostname has no effect on DNS. We have to use the --net-alias for dns.
// Hostname cannot be set with container NetworkMode. Subcontainers run is the network space of the app container
// This is done to prevent lots of up/down events and iptables locking
if (isAppContainer) {
containerOptions.Hostname = app.id;
containerOptions.HostConfig.NetworkMode = 'cloudron'; // user defined bridge network
containerOptions.HostConfig.Dns = ['172.18.0.1']; // use internal dns
containerOptions.HostConfig.DnsSearch = ['.']; // use internal dns
var capabilities = manifest.capabilities || [];
containerOptions.NetworkingConfig = {
EndpointsConfig: {
cloudron: {
IPAMConfig: {
IPv4Address: app.containerIp
},
Aliases: [ name ] // adds hostname entry with container name
}
}
};
} else {
containerOptions.HostConfig.NetworkMode = `container:${app.containerId}`; // scheduler containers must have same IP as app for various addon auth
}
// https://docs-stage.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
if (capabilities.includes('net_admin')) containerOptions.HostConfig.CapAdd.push('NET_ADMIN', 'NET_RAW');
if (capabilities.includes('mlock')) containerOptions.HostConfig.CapAdd.push('IPC_LOCK'); // mlock prevents swapping
if (!capabilities.includes('ping')) containerOptions.HostConfig.CapDrop.push('NET_RAW'); // NET_RAW is included by default by Docker
var capabilities = manifest.capabilities || [];
if (capabilities.includes('vaapi') && safe.fs.existsSync('/dev/dri')) {
containerOptions.HostConfig.Devices = [
{ PathOnHost: '/dev/dri', PathInContainer: '/dev/dri', CgroupPermissions: 'rwm' }
];
}
// https://docs-stage.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
if (capabilities.includes('net_admin')) containerOptions.HostConfig.CapAdd.push('NET_ADMIN', 'NET_RAW');
if (capabilities.includes('mlock')) containerOptions.HostConfig.CapAdd.push('IPC_LOCK'); // mlock prevents swapping
if (!capabilities.includes('ping')) containerOptions.HostConfig.CapDrop.push('NET_RAW'); // NET_RAW is included by default by Docker
containerOptions = _.extend(containerOptions, options);
if (capabilities.includes('vaapi') && safe.fs.existsSync('/dev/dri')) {
containerOptions.HostConfig.Devices = [
{ PathOnHost: '/dev/dri', PathInContainer: '/dev/dri', CgroupPermissions: 'rwm' }
];
}
gConnection.createContainer(containerOptions, function (error, container) {
if (error && error.statusCode === 409) return callback(new BoxError(BoxError.ALREADY_EXISTS, error));
if (error) return callback(new BoxError(BoxError.DOCKER_ERROR, error));
containerOptions = _.extend(containerOptions, options);
gConnection.createContainer(containerOptions, function (error, container) {
if (error && error.statusCode === 409) return callback(new BoxError(BoxError.ALREADY_EXISTS, error));
if (error) return callback(new BoxError(BoxError.DOCKER_ERROR, error));
callback(null, container);
});
callback(null, container);
});
});
}
+8 -14
View File
@@ -7,8 +7,8 @@ exports = module.exports = {
_MOCK_APP: null
};
const assert = require('assert'),
appdb = require('./appdb.js'),
const addonConfigs = require('./addonconfigs.js'),
assert = require('assert'),
apps = require('./apps.js'),
async = require('async'),
BoxError = require('./boxerror.js'),
@@ -628,24 +628,18 @@ function userSearchSftp(req, res, next) {
});
}
function verifyAppMailboxPassword(addonId, username, password, callback) {
async function verifyAppMailboxPassword(addonId, username, password) {
assert.strictEqual(typeof addonId, 'string');
assert.strictEqual(typeof username, 'string');
assert.strictEqual(typeof password, 'string');
assert.strictEqual(typeof callback, 'function');
const pattern = addonId === 'sendmail' ? 'MAIL_SMTP' : 'MAIL_IMAP';
appdb.getAppIdByAddonConfigValue(addonId, `%${pattern}_PASSWORD`, password, function (error, appId) { // search by password because this is unique for each app
if (error) return callback(error);
const appId = await addonConfigs.getAppIdByValue(addonId, `%${pattern}_PASSWORD`, password); // search by password because this is unique for each app
if (!appId) throw new BoxError(BoxError.NOT_FOUND);
appdb.getAddonConfig(appId, addonId, function (error, result) {
if (error) return callback(error);
const result = await addonConfigs.get(appId, addonId);
if (!result.some(r => r.name.endsWith(`${pattern}_USERNAME`) && r.value === username)) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
callback(null);
});
});
if (!result.some(r => r.name.endsWith(`${pattern}_USERNAME`) && r.value === username)) throw new BoxError(BoxError.INVALID_CREDENTIALS);
}
async function authenticateMailAddon(req, res, next) {
@@ -666,7 +660,7 @@ async function authenticateMailAddon(req, res, next) {
if (addonId === 'recvmail' && !domain.enabled) return next(new ldap.NoSuchObjectError(req.dn.toString()));
const [appPasswordError] = await safe(util.promisify(verifyAppMailboxPassword)(addonId, email, req.credentials || ''));
const [appPasswordError] = await safe(verifyAppMailboxPassword(addonId, email, req.credentials || ''));
if (!appPasswordError) return res.end(); // validated as app
if (appPasswordError && appPasswordError.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
+44 -42
View File
@@ -31,7 +31,8 @@ exports = module.exports = {
SERVICE_STATUS_STOPPED: 'stopped'
};
const appdb = require('./appdb.js'),
const addonConfigs = require('./addonconfigs.js'),
appdb = require('./appdb.js'),
apps = require('./apps.js'),
assert = require('assert'),
async = require('async'),
@@ -68,9 +69,13 @@ const NOOP_CALLBACK = function (error) { if (error) debug(error); };
const RMADDONDIR_CMD = path.join(__dirname, 'scripts/rmaddondir.sh');
const RESTART_SERVICE_CMD = path.join(__dirname, 'scripts/restartservice.sh');
const setAddonConfig = util.callbackify(addonConfigs.set),
unsetAddonConfig = util.callbackify(addonConfigs.unset),
getAddonConfigByName = util.callbackify(addonConfigs.getByName);
// setup can be called multiple times for the same app (configure crash restart) and existing data must not be lost
// teardown is destructive. app data stored with the addon is lost
var ADDONS = {
const ADDONS = {
turn: {
setup: setupTurn,
teardown: teardownTurn,
@@ -883,17 +888,14 @@ function startServices(existingInfra, callback) {
});
}
function getEnvironment(app, callback) {
async function getEnvironment(app) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof callback, 'function');
appdb.getAddonConfigByAppId(app.id, function (error, result) {
if (error) return callback(error);
const result = await addonConfigs.getByAppId(app.id);
if (app.manifest.addons['docker']) result.push({ name: 'CLOUDRON_DOCKER_HOST', value: `tcp://172.18.0.1:${constants.DOCKER_PROXY_PORT}` });
if (app.manifest.addons['docker']) result.push({ name: 'CLOUDRON_DOCKER_HOST', value: `tcp://172.18.0.1:${constants.DOCKER_PROXY_PORT}` });
return callback(null, result.map(function (e) { return e.name + '=' + e.value; }));
});
return result.map(function (e) { return e.name + '=' + e.value; });
}
function getContainerNamesSync(app, addons) {
@@ -978,7 +980,7 @@ function setupTurn(app, options, callback) {
debugApp(app, 'Setting up TURN');
appdb.setAddonConfig(app.id, 'turn', env, callback);
setAddonConfig(app.id, 'turn', env, callback);
});
}
@@ -989,7 +991,7 @@ function teardownTurn(app, options, callback) {
debugApp(app, 'Tearing down TURN');
appdb.unsetAddonConfig(app.id, 'turn', callback);
unsetAddonConfig(app.id, 'turn', callback);
}
function setupEmail(app, options, callback) {
@@ -1022,7 +1024,7 @@ function setupEmail(app, options, callback) {
debugApp(app, 'Setting up Email');
appdb.setAddonConfig(app.id, 'email', env, callback);
setAddonConfig(app.id, 'email', env, callback);
});
}
@@ -1033,7 +1035,7 @@ function teardownEmail(app, options, callback) {
debugApp(app, 'Tearing down Email');
appdb.unsetAddonConfig(app.id, 'email', callback);
unsetAddonConfig(app.id, 'email', callback);
}
function setupLdap(app, options, callback) {
@@ -1058,7 +1060,7 @@ function setupLdap(app, options, callback) {
debugApp(app, 'Setting up LDAP');
appdb.setAddonConfig(app.id, 'ldap', env, callback);
setAddonConfig(app.id, 'ldap', env, callback);
}
function teardownLdap(app, options, callback) {
@@ -1068,7 +1070,7 @@ function teardownLdap(app, options, callback) {
debugApp(app, 'Tearing down LDAP');
appdb.unsetAddonConfig(app.id, 'ldap', callback);
unsetAddonConfig(app.id, 'ldap', callback);
}
function setupSendMail(app, options, callback) {
@@ -1079,9 +1081,9 @@ function setupSendMail(app, options, callback) {
debugApp(app, 'Setting up SendMail');
const disabled = app.manifest.addons.sendmail.optional && !app.enableMailbox;
if (disabled) return appdb.setAddonConfig(app.id, 'sendmail', [], callback);
if (disabled) return setAddonConfig(app.id, 'sendmail', [], callback);
appdb.getAddonConfigByName(app.id, 'sendmail', '%MAIL_SMTP_PASSWORD', function (error, existingPassword) {
getAddonConfigByName(app.id, 'sendmail', '%MAIL_SMTP_PASSWORD', function (error, existingPassword) {
if (error && error.reason !== BoxError.NOT_FOUND) return callback(error);
var password = error ? hat(4 * 48) : existingPassword; // see box#565 for password length
@@ -1099,7 +1101,7 @@ function setupSendMail(app, options, callback) {
{ name: `${envPrefix}MAIL_DOMAIN`, value: app.mailboxDomain }
];
debugApp(app, 'Setting sendmail addon config to %j', env);
appdb.setAddonConfig(app.id, 'sendmail', env, callback);
setAddonConfig(app.id, 'sendmail', env, callback);
});
}
@@ -1110,7 +1112,7 @@ function teardownSendMail(app, options, callback) {
debugApp(app, 'Tearing down sendmail');
appdb.unsetAddonConfig(app.id, 'sendmail', callback);
unsetAddonConfig(app.id, 'sendmail', callback);
}
function setupRecvMail(app, options, callback) {
@@ -1120,7 +1122,7 @@ function setupRecvMail(app, options, callback) {
debugApp(app, 'Setting up recvmail');
appdb.getAddonConfigByName(app.id, 'recvmail', '%MAIL_IMAP_PASSWORD', function (error, existingPassword) {
getAddonConfigByName(app.id, 'recvmail', '%MAIL_IMAP_PASSWORD', function (error, existingPassword) {
if (error && error.reason !== BoxError.NOT_FOUND) return callback(error);
var password = error ? hat(4 * 48) : existingPassword; // see box#565 for password length
@@ -1137,7 +1139,7 @@ function setupRecvMail(app, options, callback) {
];
debugApp(app, 'Setting sendmail addon config to %j', env);
appdb.setAddonConfig(app.id, 'recvmail', env, callback);
setAddonConfig(app.id, 'recvmail', env, callback);
});
}
@@ -1148,7 +1150,7 @@ function teardownRecvMail(app, options, callback) {
debugApp(app, 'Tearing down recvmail');
appdb.unsetAddonConfig(app.id, 'recvmail', callback);
unsetAddonConfig(app.id, 'recvmail', callback);
}
function mysqlDatabaseName(appId) {
@@ -1219,7 +1221,7 @@ function setupMySql(app, options, callback) {
debugApp(app, 'Setting up mysql');
appdb.getAddonConfigByName(app.id, 'mysql', '%MYSQL_PASSWORD', function (error, existingPassword) {
getAddonConfigByName(app.id, 'mysql', '%MYSQL_PASSWORD', function (error, existingPassword) {
if (error && error.reason !== BoxError.NOT_FOUND) return callback(error);
const tmp = mysqlDatabaseName(app.id);
@@ -1257,7 +1259,7 @@ function setupMySql(app, options, callback) {
}
debugApp(app, 'Setting mysql addon config to %j', env);
appdb.setAddonConfig(app.id, 'mysql', env, callback);
setAddonConfig(app.id, 'mysql', env, callback);
});
});
});
@@ -1297,7 +1299,7 @@ function teardownMySql(app, options, callback) {
if (error) return callback(new BoxError(BoxError.ADDONS_ERROR, `Error tearing down mysql: ${error.message}`));
if (response.statusCode !== 200) return callback(new BoxError(BoxError.ADDONS_ERROR, `Error tearing down mysql. Status code: ${response.statusCode} message: ${response.body.message}`));
appdb.unsetAddonConfig(app.id, 'mysql', callback);
unsetAddonConfig(app.id, 'mysql', callback);
});
});
}
@@ -1439,7 +1441,7 @@ function setupPostgreSql(app, options, callback) {
const { database, username } = postgreSqlNames(app.id);
appdb.getAddonConfigByName(app.id, 'postgresql', '%POSTGRESQL_PASSWORD', function (error, existingPassword) {
getAddonConfigByName(app.id, 'postgresql', '%POSTGRESQL_PASSWORD', function (error, existingPassword) {
if (error && error.reason !== BoxError.NOT_FOUND) return callback(error);
const data = {
@@ -1468,7 +1470,7 @@ function setupPostgreSql(app, options, callback) {
];
debugApp(app, 'Setting postgresql addon config to %j', env);
appdb.setAddonConfig(app.id, 'postgresql', env, callback);
setAddonConfig(app.id, 'postgresql', env, callback);
});
});
});
@@ -1510,7 +1512,7 @@ function teardownPostgreSql(app, options, callback) {
if (error) return callback(new BoxError(BoxError.ADDONS_ERROR, `Network error tearing down postgresql: ${error.message}`));
if (response.statusCode !== 200) return callback(new BoxError(BoxError.ADDONS_ERROR, `Error tearing down postgresql. Status code: ${response.statusCode} message: ${response.body.message}`));
appdb.unsetAddonConfig(app.id, 'postgresql', callback);
unsetAddonConfig(app.id, 'postgresql', callback);
});
});
}
@@ -1658,10 +1660,10 @@ function setupMongoDb(app, options, callback) {
debugApp(app, 'Setting up mongodb');
appdb.getAddonConfigByName(app.id, 'mongodb', '%MONGODB_PASSWORD', function (error, existingPassword) {
getAddonConfigByName(app.id, 'mongodb', '%MONGODB_PASSWORD', function (error, existingPassword) {
if (error && error.reason !== BoxError.NOT_FOUND) return callback(error);
appdb.getAddonConfigByName(app.id, 'mongodb', '%MONGODB_DATABASE', function (error, database) {
getAddonConfigByName(app.id, 'mongodb', '%MONGODB_DATABASE', function (error, database) {
database = database || hat(8 * 8); // 16 bytes. keep this short, so as to not overflow the 127 byte index length in MongoDB < 4.4
const data = {
@@ -1694,7 +1696,7 @@ function setupMongoDb(app, options, callback) {
}
debugApp(app, 'Setting mongodb addon config to %j', env);
appdb.setAddonConfig(app.id, 'mongodb', env, callback);
setAddonConfig(app.id, 'mongodb', env, callback);
});
});
});
@@ -1709,7 +1711,7 @@ function clearMongodb(app, options, callback) {
getContainerDetails('mongodb', 'CLOUDRON_MONGODB_TOKEN', function (error, result) {
if (error) return callback(error);
appdb.getAddonConfigByName(app.id, 'mongodb', '%MONGODB_DATABASE', function (error, database) {
getAddonConfigByName(app.id, 'mongodb', '%MONGODB_DATABASE', function (error, database) {
if (error) return callback(error);
request.post(`https://${result.ip}:3000/databases/${database}/clear?access_token=${result.token}`, { json: true, rejectUnauthorized: false }, function (error, response) {
@@ -1730,7 +1732,7 @@ function teardownMongoDb(app, options, callback) {
getContainerDetails('mongodb', 'CLOUDRON_MONGODB_TOKEN', function (error, result) {
if (error) return callback(error);
appdb.getAddonConfigByName(app.id, 'mongodb', '%MONGODB_DATABASE', function (error, database) {
getAddonConfigByName(app.id, 'mongodb', '%MONGODB_DATABASE', function (error, database) {
if (error && error.reason === BoxError.NOT_FOUND) return callback(null);
if (error) return callback(error);
@@ -1738,7 +1740,7 @@ function teardownMongoDb(app, options, callback) {
if (error) return callback(new BoxError(BoxError.ADDONS_ERROR, `Error tearing down mongodb: ${error.message}`));
if (response.statusCode !== 200) return callback(new BoxError(BoxError.ADDONS_ERROR, `Error tearing down mongodb. Status code: ${response.statusCode} message: ${response.body.message}`));
appdb.unsetAddonConfig(app.id, 'mongodb', callback);
unsetAddonConfig(app.id, 'mongodb', callback);
});
});
});
@@ -1754,7 +1756,7 @@ function backupMongoDb(app, options, callback) {
getContainerDetails('mongodb', 'CLOUDRON_MONGODB_TOKEN', function (error, result) {
if (error) return callback(error);
appdb.getAddonConfigByName(app.id, 'mongodb', '%MONGODB_DATABASE', function (error, database) {
getAddonConfigByName(app.id, 'mongodb', '%MONGODB_DATABASE', function (error, database) {
if (error) return callback(error);
const url = `https://${result.ip}:3000/databases/${database}/backup?access_token=${result.token}`;
@@ -1775,7 +1777,7 @@ function restoreMongoDb(app, options, callback) {
getContainerDetails('mongodb', 'CLOUDRON_MONGODB_TOKEN', function (error, result) {
if (error) return callback(error);
appdb.getAddonConfigByName(app.id, 'mongodb', '%MONGODB_DATABASE', function (error, database) {
getAddonConfigByName(app.id, 'mongodb', '%MONGODB_DATABASE', function (error, database) {
if (error) return callback(error);
const readStream = fs.createReadStream(dumpPath('mongodb', app.id));
@@ -1853,7 +1855,7 @@ function setupProxyAuth(app, options, callback) {
if (!enabled) return callback();
const env = [ { name: 'CLOUDRON_PROXY_AUTH', value: '1' } ];
appdb.setAddonConfig(app.id, 'proxyauth', env, callback);
setAddonConfig(app.id, 'proxyauth', env, callback);
}
function teardownProxyAuth(app, options, callback) {
@@ -1861,7 +1863,7 @@ function teardownProxyAuth(app, options, callback) {
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
appdb.unsetAddonConfig(app.id, 'proxyauth', callback);
unsetAddonConfig(app.id, 'proxyauth', callback);
}
function startRedis(existingInfra, callback) {
@@ -1906,7 +1908,7 @@ function setupRedis(app, options, callback) {
const redisName = 'redis-' + app.id;
appdb.getAddonConfigByName(app.id, 'redis', '%REDIS_PASSWORD', function (error, existingPassword) {
getAddonConfigByName(app.id, 'redis', '%REDIS_PASSWORD', function (error, existingPassword) {
if (error && error.reason !== BoxError.NOT_FOUND) return callback(error);
const redisPassword = options.noPassword ? '' : (error ? hat(4 * 48) : existingPassword); // see box#362 for password length
@@ -1957,7 +1959,7 @@ function setupRedis(app, options, callback) {
shell.exec('startRedis', cmd, next);
});
},
appdb.setAddonConfig.bind(null, app.id, 'redis', env),
setAddonConfig.bind(null, app.id, 'redis', env),
waitForContainer.bind(null, 'redis-' + app.id, 'CLOUDRON_REDIS_TOKEN')
], function (error) {
if (error) debug('Error setting up redis: ', error);
@@ -1999,7 +2001,7 @@ function teardownRedis(app, options, callback) {
rimraf(path.join(paths.LOG_DIR, `redis-${app.id}`), function (error) {
if (error) debugApp(app, 'cannot cleanup logs:', error);
appdb.unsetAddonConfig(app.id, 'redis', callback);
unsetAddonConfig(app.id, 'redis', callback);
});
});
});
@@ -2183,5 +2185,5 @@ function teardownOauth(app, options, callback) {
debugApp(app, 'teardownOauth');
appdb.unsetAddonConfig(app.id, 'oauth', callback);
unsetAddonConfig(app.id, 'oauth', callback);
}
+80
View File
@@ -0,0 +1,80 @@
/* global it:false */
/* global describe:false */
/* global before:false */
/* global after:false */
'use strict';
const addonConfigs = require('../addonconfigs.js'),
common = require('./common.js'),
expect = require('expect.js');
describe('Addon config', function () {
const { setup, cleanup, app } = common;
before(setup);
after(cleanup);
it('returns empty addon config array for invalid app', async function () {
const results = await addonConfigs.getByAppId('randomid');
expect(results).to.eql([ ]);
});
it('set succeeds', async function () {
await addonConfigs.set(app.id, 'addonid1', [ { name: 'ENV1', value: 'env' }, { name: 'ENV2', value: 'env2' } ]);
await addonConfigs.set(app.id, 'addonid2', [ { name: 'ENV3', value: 'env' } ]);
});
it('get succeeds', async function () {
const results = await addonConfigs.get(app.id, 'addonid1');
expect(results).to.eql([ { name: 'ENV1', value: 'env' }, { name: 'ENV2', value: 'env2' } ]);
});
it('getByAppId succeeds', async function () {
const results = await addonConfigs.getByAppId(app.id);
expect(results).to.eql([ { name: 'ENV1', value: 'env' }, { name: 'ENV2', value: 'env2' }, { name: 'ENV3', value: 'env' } ]);
});
it('getByName succeeds', async function () {
const value = await addonConfigs.getByName(app.id, 'addonid1', 'ENV2');
expect(value).to.be('env2');
});
it('getByName of unknown value succeeds', async function () {
const value = await addonConfigs.getByName(app.id, 'addonid1', 'ENVRANDOM');
expect(value).to.be(null);
});
it('getAppIdByValue succeeds', async function () {
const appId = await addonConfigs.getAppIdByValue('addonid1', 'ENV1', 'env');
expect(appId).to.be(app.id);
});
it('getAppIdByValue pattern succeeds', async function () {
const appId = await addonConfigs.getAppIdByValue('addonid1', '%ENV1', 'env');
expect(appId).to.be(app.id);
});
it('getAppIdByValue pattern of unknown succeeds', async function () {
const appId = await addonConfigs.getAppIdByValue('addonid1', '%ENV1', 'envx');
expect(appId).to.be(null);
});
it('unset succeeds', async function () {
await addonConfigs.unset(app.id, 'addonid1');
});
it('unsetAddonConfig did remove configs', async function () {
const results = await addonConfigs.getByAppId(app.id);
expect(results).to.eql([ { name: 'ENV3', value: 'env' }]);
});
it('unsetByAppId succeeds', async function () {
await addonConfigs.unsetByAppId(app.id);
});
it('unsetByAppId did remove configs', async function () {
const results = await addonConfigs.getByAppId(app.id);
expect(results).to.eql([ ]);
});
});
-83
View File
@@ -309,89 +309,6 @@ describe('database', function () {
done();
});
});
it('return empty addon config array for invalid app', function (done) {
appdb.getAddonConfigByAppId('randomid', function (error, results) {
expect(error).to.be(null);
expect(results).to.eql([ ]);
done();
});
});
it('setAddonConfig succeeds', function (done) {
appdb.setAddonConfig(APP_1.id, 'addonid1', [ { name: 'ENV1', value: 'env' }, { name: 'ENV2', value: 'env2' } ], function (error) {
expect(error).to.be(null);
done();
});
});
it('setAddonConfig succeeds', function (done) {
appdb.setAddonConfig(APP_1.id, 'addonid2', [ { name: 'ENV3', value: 'env' } ], function (error) {
expect(error).to.be(null);
done();
});
});
it('getAddonConfig succeeds', function (done) {
appdb.getAddonConfig(APP_1.id, 'addonid1', function (error, results) {
expect(error).to.be(null);
expect(results).to.eql([ { name: 'ENV1', value: 'env' }, { name: 'ENV2', value: 'env2' } ]);
done();
});
});
it('getAddonConfigByAppId succeeds', function (done) {
appdb.getAddonConfigByAppId(APP_1.id, function (error, results) {
expect(error).to.be(null);
expect(results).to.eql([ { name: 'ENV1', value: 'env' }, { name: 'ENV2', value: 'env2' }, { name: 'ENV3', value: 'env' } ]);
done();
});
});
it('getAddonConfigByName succeeds', function (done) {
appdb.getAddonConfigByName(APP_1.id, 'addonid1', 'ENV2', function (error, value) {
expect(error).to.be(null);
expect(value).to.be('env2');
done();
});
});
it('getAddonConfigByName of unknown value succeeds', function (done) {
appdb.getAddonConfigByName(APP_1.id, 'addonid1', 'NOPE', function (error) {
expect(error.reason).to.be(BoxError.NOT_FOUND);
done();
});
});
it('unsetAddonConfig succeeds', function (done) {
appdb.unsetAddonConfig(APP_1.id, 'addonid1', function (error) {
expect(error).to.be(null);
done();
});
});
it('unsetAddonConfig did remove configs', function (done) {
appdb.getAddonConfigByAppId(APP_1.id, function (error, results) {
expect(error).to.be(null);
expect(results).to.eql([ { name: 'ENV3', value: 'env' }]);
done();
});
});
it('unsetAddonConfigByAppId succeeds', function (done) {
appdb.unsetAddonConfigByAppId(APP_1.id, function (error) {
expect(error).to.be(null);
done();
});
});
it('unsetAddonConfigByAppId did remove configs', function (done) {
appdb.getAddonConfigByAppId(APP_1.id, function (error, results) {
expect(error).to.be(null);
expect(results).to.eql([ ]);
done();
});
});
});
describe('importFromFile', function () {
+4 -8
View File
@@ -6,7 +6,7 @@
'use strict';
const appdb = require('../appdb.js'),
const addonConfigs = require('../addonconfigs.js'),
async = require('async'),
common = require('./common.js'),
constants = require('../constants.js'),
@@ -15,8 +15,7 @@ const appdb = require('../appdb.js'),
ldap = require('ldapjs'),
ldapServer = require('../ldap.js'),
mail = require('../mail.js'),
safe = require('safetydance'),
util = require('util');
safe = require('safetydance');
async function ldapBind(dn, password) {
return new Promise((resolve, reject) => {
@@ -417,9 +416,7 @@ describe('Ldap', function () {
});
it('allows with valid password', async function () {
const setAddonConfig = util.promisify(appdb.setAddonConfig);
await setAddonConfig(app.id, 'sendmail', [{ name: 'MAIL_SMTP_USERNAME', value : `${app.location}.app@${domain.domain}` }, { name: 'MAIL_SMTP_PASSWORD', value : 'sendmailpassword' }]),
await addonConfigs.set(app.id, 'sendmail', [{ name: 'MAIL_SMTP_USERNAME', value : `${app.location}.app@${domain.domain}` }, { name: 'MAIL_SMTP_PASSWORD', value : 'sendmailpassword' }]),
await ldapBind(`cn=${app.location}.app@${domain.domain},ou=sendmail,dc=cloudron`, 'sendmailpassword');
});
@@ -469,8 +466,7 @@ describe('Ldap', function () {
});
it('allows with valid password', async function () {
const setAddonConfig = util.promisify(appdb.setAddonConfig);
await setAddonConfig(app.id, 'recvmail', [{ name: 'MAIL_IMAP_USERNAME', value : `${app.location}.app@${domain.domain}` }, { name: 'MAIL_IMAP_PASSWORD', value : 'recvmailpassword' }]),
await addonConfigs.set(app.id, 'recvmail', [{ name: 'MAIL_IMAP_USERNAME', value : `${app.location}.app@${domain.domain}` }, { name: 'MAIL_IMAP_PASSWORD', value : 'recvmailpassword' }]),
await ldapBind(`cn=${app.location}.app@${domain.domain},ou=recvmail,dc=cloudron`, 'recvmailpassword');
});
});