UNPKG

cassandra-migration

Version:
364 lines (336 loc) 14.4 kB
// Generated by CoffeeScript 1.12.7 (function() { var FS, Q, _, applyMigration, cassandra, createVersionTable, debugMode, durations, firstNodeWithPort, getCassandraClient, getSchemaVersion, listMigrations, logDebug, logError, logInfo, migrate, moduleVersion, parseCassandraHosts, program, quietMode, readConfig, runQuery, runScript, sleep; _ = require('lodash'); Q = require('q'); FS = require('fs'); program = require('commander'); moduleVersion = require('../package.json').version; cassandra = require('cassandra-driver'); durations = require('durations'); quietMode = false; debugMode = false; sleep = function(ms) { return new Promise(function(resolve) { setTimeout(resolve, ms); }); }; logError = function(message, error) { var errorMessage, stack; errorMessage = error != null ? ": " + error : ''; stack = (error != null) && debugMode ? "\n" + error.stack : ''; return console.error("" + message + errorMessage + stack); }; logInfo = function(message) { if (!quietMode) { return console.log(message); } }; logDebug = function(message) { if (!quietMode && debugMode === true) { return console.log(message); } }; readConfig = function(configFile) { return Q.nfcall(FS.readFile, configFile, 'utf-8').then(function(rawConfig) { var config, d, error; d = Q.defer(); try { config = JSON.parse(rawConfig); d.resolve(config); } catch (error1) { error = error1; d.reject(error); } return d.promise; }).then(function(config) { var d; d = Q.defer(); if (config.cassandra != null) { d.resolve(config); } else { d.reject(new Error("Cassandra configuration not supplied.")); } return d.promise; }); }; listMigrations = function(config) { var d, migrationsDir; d = Q.defer(); migrationsDir = config.migrationsDir; if (migrationsDir == null) { d.reject(new Error("The config did not contain a migrationsDir property.")); } else if (!FS.existsSync(migrationsDir)) { d.reject(new Error("Migrations directory does not exist.")); } else { FS.readdir(migrationsDir, function(error, files) { var migrationFiles; if (error != null) { return d.reject(new Error("Error listing migrations directory contents: " + error, error)); } else { migrationFiles = _(files).filter(function(fileName) { return _.endsWith(fileName.toLowerCase(), '.cql'); }).filter(function(fileName) { var filePath; filePath = migrationsDir + "/" + fileName; return FS.statSync(filePath).isFile(); }).map(function(fileName) { var file, version; version = fileName.split('__')[0]; file = migrationsDir + "/" + fileName; return [file, version]; }).filter(function(arg) { var file, version; file = arg[0], version = arg[1]; return !isNaN(version); }).map(function(arg) { var file, version; file = arg[0], version = arg[1]; return [file, parseInt(version)]; }).value(); if (_(migrationFiles).size() > 0) { return d.resolve(migrationFiles); } else { return d.reject(new Error("No migration files found")); } } }); } return d.promise.then(function(files) { return files; }); }; getCassandraClient = function(config) { var cassandraConfig, client, d, dcAwareRoundRobinPolicy, error, nodeWhiteList, tokenAwarePolicy, whiteListPolicy; d = Q.defer(); try { cassandraConfig = config.cassandra; if ((cassandraConfig.datacenterName != null) && cassandraConfig.useSingleNode) { dcAwareRoundRobinPolicy = new cassandra.policies.loadBalancing.DCAwareRoundRobinPolicy(cassandraConfig.datacenterName); tokenAwarePolicy = new cassandra.policies.loadBalancing.TokenAwarePolicy(dcAwareRoundRobinPolicy); nodeWhiteList = [firstNodeWithPort(cassandraConfig)]; whiteListPolicy = new cassandra.policies.loadBalancing.WhiteListPolicy(tokenAwarePolicy, nodeWhiteList); cassandraConfig.policies = { loadBalancing: whiteListPolicy }; } client = new cassandra.Client(cassandraConfig); client.connect(function(error) { if (error != null) { return d.reject(error); } else { logDebug("Connected to Cassandra."); return d.resolve(client); } }); } catch (error1) { error = error1; d.reject(new Error("Error creating Cassandra client: " + error, error)); } return d.promise; }; firstNodeWithPort = function(cassandraConfig) { var firstNode; firstNode = cassandraConfig.contactPoints[0]; if (firstNode.match(/[^\:]+:[0-9]+/)) { return firstNode; } else { return firstNode + ":" + cassandraConfig.protocolOptions.port; } }; createVersionTable = function(config, client, keyspace) { var d, versionQuery; d = Q.defer(); versionQuery = "SELECT release_version FROM system.local"; client.execute(versionQuery, function(error, results) { var cassandraVersion, isVersion3orAbove, schemaKeyspace, tableNameColumn, tableQuery, tablesTable; cassandraVersion = _(results.rows).map(function(row) { return row.release_version; }).first(); if (error != null) { d.reject(error); } if (cassandraVersion == null) { return d.reject(new Error("Could not determine the version of Cassandra!")); } else { logDebug("Cassandra version: " + cassandraVersion); isVersion3orAbove = _.startsWith(cassandraVersion, "3.") || _.startsWith(cassandraVersion, "4."); schemaKeyspace = isVersion3orAbove ? "system_schema" : "system"; tablesTable = isVersion3orAbove ? "tables" : "schema_columnfamilies"; tableNameColumn = isVersion3orAbove ? "table_name" : "columnfamily_name"; logDebug("schemaKeyspace: " + schemaKeyspace); logDebug("tablesTable: " + tablesTable); logDebug("tableNameColumn: " + tableNameColumn); tableQuery = "SELECT " + tableNameColumn + " \nFROM " + schemaKeyspace + "." + tablesTable + " \nWHERE keyspace_name='" + keyspace + "'"; return client.execute(tableQuery, function(error, results) { var createQuery, tableNames; tableNames = _(results.rows).map(function(row) { return row[tableNameColumn]; }).value(); if (error != null) { return d.reject(error); } else if (_(tableNames).filter(function(tableName) { return tableName === 'schema_version'; }).size() > 0) { logDebug("Table 'schema_version' already exists."); return d.resolve(client); } else { logDebug(""); createQuery = "CREATE TABLE " + keyspace + ".schema_version (\n zero INT,\n version INT,\n migration_timestamp TIMESTAMP, \n\n PRIMARY KEY (zero, version)\n) WITH CLUSTERING ORDER BY (version DESC)"; logDebug("creating the schema_version table..."); return client.execute(createQuery, function(error, results) { if (error != null) { return d.reject(new Error("Error creating the schema_version table: " + error, error)); } else { return d.resolve(client); } }); } }); } }); return d.promise; }; getSchemaVersion = function(config, client, keyspace) { return createVersionTable(config, client, keyspace).then(function() { var d, versionQuery; d = Q.defer(); logDebug("Fetching version info..."); versionQuery = "SELECT version FROM " + keyspace + ".schema_version LIMIT 1"; client.execute(versionQuery, function(error, results) { var ref, ref1, ref2, version; if (error != null) { logError("Error reading version information from the version table", error); return d.reject(new Error("Error reading version information from the version table: " + error, error)); } else if (_(results.rows).size() > 0) { version = (ref = (ref1 = _(results.rows)) != null ? (ref2 = ref1.first()) != null ? ref2.version : void 0 : void 0) != null ? ref : 0; version = parseInt(version); logDebug("Current version is " + version); return d.resolve(version); } else { logDebug("Current version is 0"); return d.resolve(0); } }); return d.promise; }); }; runQuery = function(config, client, query, version) { var d; d = Q.defer(); client.execute(query, function(error, results) { logDebug("running query: " + query); if (error != null) { return d.reject(new Error("Error applying migration " + version + ": " + error, error)); } else { return d.resolve(version); } }); return d.promise; }; applyMigration = function(config, client, keyspace, file, version, interval) { var cql, queryStrings; logInfo("Applying migration: " + file); queryStrings = _.trim(FS.readFileSync(file, 'utf-8')).split('---'); cql = ("INSERT INTO " + keyspace + ".schema_version") + " (zero, version, migration_timestamp)" + (" VALUES (0, " + version + ", '" + (new Date().getTime()) + "');"); queryStrings.push(cql); return queryStrings.reduce((function(promiseChain, queryString) { return promiseChain.then(function() { return runQuery(config, client, queryString, version).then(function() { return sleep(interval).then(function() { return version; }); }); }); }), Promise.resolve()); }; migrate = function(config, client, keyspace, migrationFiles, schemaVersion, interval) { var migrationFunctions, migrations, versionString, versions; migrations = _(migrationFiles).filter(function(arg) { var file, version; file = arg[0], version = arg[1]; return version > schemaVersion && version <= config.targetVersion; }).sortBy(function(arg) { var file, version; file = arg[0], version = arg[1]; return version; }).value(); versionString = config.targetVersion === Number.MAX_VALUE ? "unlimited" : config.targetVersion; logDebug("Migrations to be applied: " + migrations + " (target version is " + versionString + ")"); if (_(migrations).size() > 0) { versions = _(migrations).map(function(arg) { var file, version; file = arg[0], version = arg[1]; return version; }).value(); versions.unshift(schemaVersion); logInfo("Migrating database " + (_(versions).join(" -> ")) + " ..."); migrationFunctions = _(migrations).map(function(arg) { var file, version; file = arg[0], version = arg[1]; return function() { return applyMigration(config, client, keyspace, file, version, interval); }; }).value(); return migrationFunctions.reduce(Q.when, Q(schemaVersion)).then(function(version) { console.log("All migrations complete. Schema is now at version " + version + "."); return version; }); } else { console.log("No new migrations. Schema version is " + schemaVersion); return Q(schemaVersion); } }; parseCassandraHosts = function(val) { return val.split(','); }; runScript = function() { var cassandraClient, code, configFile; program.version(moduleVersion).usage('[options] <config_file>').option('-d, --debug', 'Increase verbosity and error detail').option('-h, --hosts <hosts>', 'A comma separated list of cassandra hosts', parseCassandraHosts).option('-k, --keyspace <keyspace>', 'Cassandra keyspace used for migration and schema_version table').option('-q, --quiet', 'Silence non-error output (default is false)').option('-i, --interval <interval>', 'Milliseconds to wait between migration queries are executed').option('-t, --target-version <version>', 'Maximum migration version to apply (default runs all migrations)').parse(process.argv); configFile = _(program.args).last(); code = 1; cassandraClient = void 0; return readConfig(configFile).then(function(config) { var interval, keyspace, ref, ref1, ref2, ref3, ref4, ref5; config.quiet = (ref = program.quiet) != null ? ref : config.quiet; config.debug = (ref1 = program.debug) != null ? ref1 : config.debug; config.cassandra.keyspace = (ref2 = program.keyspace) != null ? ref2 : config.cassandra.keyspace; config.targetVersion = (ref3 = program.targetVersion) != null ? ref3 : Number.MAX_VALUE; config.cassandra.contactPoints = (ref4 = program.hosts) != null ? ref4 : config.cassandra.contactPoints; quietMode = config.quiet; debugMode = config.debug; keyspace = config.cassandra.keyspace; interval = (ref5 = program.interval) != null ? ref5 : 0; if (config.auth != null) { logDebug("Connecting with simple user authentication."); config.cassandra.authProvider = new cassandra.auth.PlainTextAuthProvider(config.auth.username, config.auth.password); } else { logDebug("Connecting without authentication."); } return Q.all([listMigrations(config), getCassandraClient(config)]).spread(function(migrationFiles, client) { cassandraClient = client; return getSchemaVersion(config, client, keyspace).then(function(schemaVersion) { return migrate(config, client, keyspace, migrationFiles, schemaVersion, interval); }).then(function(version) { return code = 0; })["catch"](function(error) { return logError("Migration Error", error); }); }); })["catch"](function(error) { return logError("Error reading configuration file", error); })["finally"](function() { if (cassandraClient != null) { cassandraClient.shutdown; } return process.exit(code); }); }; module.exports = { run: runScript, listMigrations: listMigrations }; if (require.main === module) { runScript(); } }).call(this);