Created
January 26, 2017 22:34
-
-
Save prydonius/f9c92389916f046635db07d231fe449c to your computer and use it in GitHub Desktop.
MongoDB nami package (v3.4.1-1)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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/*" | |
] | |
} | |
] | |
} | |
] | |
} | |
] | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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