Skip to content

Instantly share code, notes, and snippets.

@prydonius
Created January 26, 2017 22:34
Show Gist options
  • Save prydonius/f9c92389916f046635db07d231fe449c to your computer and use it in GitHub Desktop.
Save prydonius/f9c92389916f046635db07d231fe449c to your computer and use it in GitHub Desktop.
MongoDB nami package (v3.4.1-1)
{
"id": "com.bitnami.mongodb",
"name": "mongodb",
"extends": [
"Service"
],
"revision": "1",
"author": {
"name": "Bitnami",
"url": "https://bitnami.com"
},
"version": "3.4.1",
"properties": {
"mongodbPort": {
"default": 27017,
"description": "MongoDB Port"
},
"database": {
"default": "",
"description": "Database to create"
},
"username": {
"default": "",
"description": "MongoDB custom user"
},
"password": {
"default": "",
"type": "password",
"description": "MongoDB custom user password"
},
"rootPassword": {
"default": "",
"type": "password",
"description": "MongoDB admin password"
},
"replicaSetMode": {
"default": "",
"description": "MongoDB replica set mode",
"type": "choice",
"validValues": [
"primary",
"secondary",
"arbiter",
""
]
},
"replicaSetKey": {
"default": "",
"description": "MongoDB key for replica set authentication"
},
"replicaSetName": {
"default": "replicaset",
"description": "MongoDB replica set name"
},
"primaryHost": {
"default": "",
"description": "MongoDB primary host"
},
"primaryPort": {
"default": "27017",
"description": "MongoDB primary port"
},
"primaryRootUser": {
"default": "root",
"description": "MongoDB primary root user"
},
"primaryRootPassword": {
"default": "",
"description": "MongoDB primary root user password"
},
"systemUser": {
"value": "mongo"
},
"systemGroup": {
"value": "mongo"
},
"dataToPersist": {
"value": [
"{{$app.confDir}}",
"{{$app.dataDir}}"
]
},
"persistDir": {
"description": "Directory to backup application folders",
"default": "/bitnami/mongodb"
},
"monitFile": {
"value": "extra/monit.conf"
},
"logrotateFile": {
"value": "extra/logrotate.conf"
}
},
"service": {
"confFile": "{{$app.confDir}}/mongodb.conf",
"pidFile": "{{$app.tmpDir}}/mongodb.pid",
"logFile": "{{$app.logsDir}}/mongodb.log",
"socketFile": "{{$app.tmpDir}}/mongodb-{{$app.mongodbPort}}.sock",
"ports": [
"{{$app.mongodbPort}}"
],
"start": {
"timeout": 180,
"wait": 5,
"username": "mongo",
"command": "{{$app.installdir}}/bin/mongod --config {{$app.confFile}}"
}
},
"installation": {
"prefix": "mongodb",
"packaging": {
"components": [
{
"name": "mongodb",
"owner": "root",
"folders": [
{
"name": "mongodb",
"files": [
{
"origin": [
"files/mongodb/*"
]
}
]
}
]
}
]
}
}
}
'use strict';
const _ = require('lodash');
const componentFunctions = require('./lib/component')($app);
const networkFunctions = require('./lib/network')($app);
const mongodbFunctions = require('./lib/databases').mongodb({binDir: $app.binDir});
$app.helpers.validateInputs = function() {
if ($app.replicaSetMode.match(/secondary|arbiter/) && _.isEmpty($app.primaryHost)) {
throw new Error('In order to configure MongoDB as secondary or arbiter node ' +
'you need to provide the --primaryHost property');
}
const replicaSetAuthMessage = 'In order to configure MongoDB replica set authentication ' +
'you need to provide the --replicaSetKey on every node, ' +
'specify --rootPassword in the primary node and --primaryRootPassword in the rest of nodes';
const replicaSetSlavesAuthProperties = [$app.primaryRootPassword, $app.replicaSetKey];
if ($app.replicaSetMode.match(/secondary|arbiter/)) {
if (!replicaSetSlavesAuthProperties.every(_.isEmpty) && replicaSetSlavesAuthProperties.some(_.isEmpty)) {
throw new Error(replicaSetAuthMessage);
}
if (!_.isEmpty($app.rootPassword)) throw new Error(replicaSetAuthMessage);
}
const replicaSetMasterAuthProperties = [$app.rootPassword, $app.replicaSetKey];
if ($app.replicaSetMode.match(/primary/)) {
if (!replicaSetMasterAuthProperties.every(_.isEmpty) && replicaSetMasterAuthProperties.some(_.isEmpty)) {
throw new Error(replicaSetAuthMessage);
}
if (!_.isEmpty($app.primaryRootPassword)) throw new Error(replicaSetAuthMessage);
}
};
$app.helpers.configure = function(confProperties) {
_.each(confProperties, (value, key) => {
$file.substitute($app.confFile, new RegExp(`#?${key}.*`), `${key}: ${value}`,
{type: 'regexp', abortOnUnmatch: true, global: false});
});
};
$app.helpers.enableAuth = function() {
if (!_.isEmpty($app.rootPassword) || !_.isEmpty($app.password)) {
if (!$file.contains($app.confFile, /^[\s]*authorization: disabled/)) {
$app.info('==> Enabling authentication...');
const confProperties = {authorization: 'enabled',
enableLocalhostAuthBypass: 'false'};
$app.helpers.configure(confProperties);
}
}
};
$app.helpers.createUser = function() {
if (!_.isEmpty($app.rootPassword) && !$app.replicaSetMode.match(/secondary|arbiter/)) {
$app.info('==> Creating root user...');
mongodbFunctions.createUser('root', $app.rootPassword, 'admin', 'root');
}
if (!_.isEmpty($app.username)) {
if (!_.isEmpty($app.password) && !_.isEmpty($app.database)) {
$app.info(`==> Creating ${$app.username} user...`);
mongodbFunctions.createUser($app.username, $app.password, $app.database,
{role: 'readWrite', db: $app.database});
} else {
throw new Error('If you defined an username you must define a password and a database too');
}
} else {
if (!_.isEmpty($app.password) || !_.isEmpty($app.database)) {
throw new Error('If you defined a password or a database you should define an username too');
}
}
$app.helpers.enableAuth();
};
$app.helpers.configureReplicaSet = function(replicaSetProperties, connectionProperties) {
$app.info('==> Configuring MongoDB replica set');
this.configure({replication: '', replSetName: replicaSetProperties.name});
$app.restart();
const node = networkFunctions.getMachineIp();
switch (replicaSetProperties.mode) {
case 'primary': {
this.configurePrimary(node, _.assign({}, connectionProperties, {host: '127.0.0.1', port: $app.mongodbPort}));
break;
}
case 'secondary': {
this.configureSecondary(node, connectionProperties);
break;
}
case 'arbiter': {
this.configureArbiter(node, connectionProperties);
break;
}
default: {
throw new Error('Invalid replica set mode. Available options are \'primary/secondary/arbiter\'');
}
}
};
$app.helpers.configurePrimary = function(node, connectionProperties) {
$app.info('==> Configuring MongoDB primary node');
networkFunctions.waitForService('127.0.0.1', $app.mongodbPort);
const cfg = {
'_id': $app.replicaSetName,
'members': [{'_id': 0, 'host': `${node}:${$app.mongodbPort}`, 'priority': 5}]
};
mongodbFunctions.execute(`rs.initiate(${JSON.stringify(cfg)})`, connectionProperties);
};
$app.helpers.configureSecondary = function(node, connectionProperties) {
$app.info('==> Configuring MongoDB secondary node');
this.waitForPrimaryNode(connectionProperties);
const isSecondaryNodePending = function() {
const streams = mongodbFunctions.execute(`rs.add('${node}')`, connectionProperties);
const isPending = !_.includes(streams.stdout, '"ok" : 1');
return isPending;
};
// We need to retry this function because the primary node could not be running
$util.retryWhile(isSecondaryNodePending, {timeout: 90, step: 5});
// We also need to wait a confirmation from the primary so the secondary knows that has been added to replica set
this.waitConfirmation(node, connectionProperties);
};
$app.helpers.configureArbiter = function(node, connectionProperties) {
$app.info('==> Configuring MongoDB arbiter node');
this.configure({mmapv1: '',
smallFiles: 'true',
enabled: 'false'});
this.waitForPrimaryNode(connectionProperties);
const isArbiterNodePending = function() {
const streams = mongodbFunctions.execute(`rs.addArb('${node}')`, connectionProperties);
const isPending = !_.includes(streams.stdout, '"ok" : 1');
return isPending;
};
// We need to retry this function because the primary node could not be running
$util.retryWhile(isArbiterNodePending, {timeout: 90, step: 5});
// We also need to wait a confirmation from the primary so the arbiter knows that has been added to replica set
this.waitConfirmation(node, connectionProperties);
};
$app.helpers.waitForPrimaryNode = function(connectionProperties) {
$app.debug('Waiting for primary node...');
mongodbFunctions.checkConnection({
user: $app.primaryRootUser,
password: $app.primaryRootPassword,
database: 'admin',
host: $app.primaryHost,
port: $app.primaryPort
});
/* eslint-disable consistent-return */
const isPrimaryNodeUp = function() {
try {
$app.debug(`==> Validating ${$app.primaryHost} as primay node...`);
const streams = mongodbFunctions.execute('db.isMaster().ismaster', connectionProperties);
const isPrimaryUp = _.isEqual(streams.stdout, 'true');
return isPrimaryUp;
} catch (err) {
$app.debug(`[isPrimaryNodeUp] ERROR: ${err}`);
}
};
/* eslint-enable consistent-return */
$app.trace(`[waitForPrimaryNode] Waiting for primary to be ready`);
if (!$util.retryWhile(isPrimaryNodeUp, {timeout: 90, step: 5})) {
throw new Error(`Unable to validate ${$app.primaryHost} as primary node in the replica set scenario`);
}
};
$app.helpers.waitConfirmation = function(node, connectionProperties) {
/* eslint-disable consistent-return */
const isNodeConfirmed = function() {
try {
const streams = mongodbFunctions.execute('rs.status().members', connectionProperties);
const isNodePresent = !_.includes(streams.stdout, node);
return isNodePresent;
} catch (err) {
$app.trace(`[isNodeConfirmed] ERROR: ${err}`);
}
};
/* eslint-enable consistent-return */
$app.trace(`[waitConfirmation] Waiting until ${node} is added to the replica set`);
if (!$util.retryWhile(isNodeConfirmed, {timeout: 90, step: 5})) {
throw new Error(`Unable to confirm that ${node} has been added to the replica set`);
}
};
$app.helpers.configureKeyFile = function(keyFile, key) {
$app.info('==> Writing keyfile for replica set authentication');
try {
$file.write(keyFile, key);
} catch (e) {
throw new Error(`Unable to write key in ${keyFile}: ${e}`);
}
this.configureKeyFilePermissions(keyFile);
this.configure({authorization: 'enabled', keyFile: keyFile});
};
$app.helpers.configureKeyFilePermissions = function(keyFile) {
componentFunctions.configurePermissions([{
path: keyFile, user: $app.systemUser, mod: '400'
}]);
};
$app.helpers.populatePrintProperties = function() {
const properties = {};
if (!$app.replicaSetMode.match(/secondary|arbiter/)) {
properties['Root Password'] = $app.rootPassword;
}
if ($app.username && $app.password && $app.database) {
properties.Username = $app.username;
properties.Password = $app.password;
properties.Database = $app.database;
}
if (!_.isEmpty($app.replicaSetMode)) {
properties['Replication Mode'] = $app.replicaSetMode;
if ($app.replicaSetMode.match(/secondary|arbiter/)) {
properties['Primary Host'] = $app.primaryHost;
properties['Primary Port'] = $app.primaryPort;
properties['Primary Root User'] = $app.primaryRootUser;
properties['Primary Root Password'] = $app.primaryRootPassword;
}
}
return properties;
};
'use strict';
const _ = require('lodash');
const volumeFunctions = require('./lib/volume')($app);
const componentFunctions = require('./lib/component')($app);
$app.postInstallation = function() {
const keyFile = $file.join($app.confDir, 'keyfile');
$os.addGroup($app.systemGroup);
$os.addUser($app.systemUser, {gid: $app.systemGroup});
_.each([$app.tmpDir, $app.logsDir], function(folder) {
$file.mkdir(folder, {owner: $app.systemUser, group: $app.systemGroup});
});
if (!volumeFunctions.isInitialized($app.persistDir)) {
$app.helpers.validateInputs();
$file.mkdir($file.join($app.dataDir, 'db'), {owner: $app.systemUser});
$hb.renderToFile('mongodb.conf.tpl', $app.confFile);
$app.start();
$app.helpers.createUser();
if (!_.isEmpty($app.replicaSetMode)) {
const connectionProperties = {
host: $app.primaryHost,
port: $app.primaryPort,
database: 'admin',
user: 'root',
password: $app.primaryRootPassword || $app.rootPassword || ''
};
const replicaSetProperties = {
name: $app.replicaSetName,
mode: $app.replicaSetMode
};
// https://docs.mongodb.com/manual/tutorial/deploy-replica-set-with-keyfile-access-control/
if (!_.isEmpty($app.replicaSetKey)) $app.helpers.configureKeyFile(keyFile, $app.replicaSetKey);
$app.helpers.configureReplicaSet(replicaSetProperties, connectionProperties);
}
$app.stop();
volumeFunctions.prepareDataToPersist($app.dataToPersist);
} else {
volumeFunctions.restorePersistedData($app.dataToPersist);
}
// Configure permissions for tmp and logs folders
componentFunctions.configurePermissions([$app.tmpDir, $app.logsDir], {
user: $app.systemUser, group: $app.systemGroup
});
// Restrict permissions for conf and data folders
componentFunctions.configurePermissions([{
path: $app.confDir, mod: {file: '644', directory: '755'}
}, {
path: $app.dataDir, user: $app.systemGroup, group: $app.systemGroup, mod: {file: '644', directory: '755'}
}]);
// Restrict permissions for the keyfile whether it exists
if ($file.exists(keyFile)) $app.helpers.configureKeyFilePermissions(keyFile);
componentFunctions.createExtraConfigurationFiles([
{type: 'monit', path: $app.monitFile, params: {service: 'mongodb', pidFile: $app.pidFile}},
{type: 'logrotate', path: $app.logrotateFile, params: {logPath: $file.join($app.logsDir, '*log')}}
]);
componentFunctions.printProperties($app.helpers.populatePrintProperties());
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment