UNPKG

rancher-gaucho

Version:

A JS version of etlweather/gaucho Rancher 1.x commandline scripts

638 lines (581 loc) 22.7 kB
#!/usr/bin/env node const yargs = require('yargs') const requests = require('axios') const WebSocket = require('ws') const util = require('util') const URL_SERVICE = '/services/' const URL_ENVIRONMENT = '/projects/' let HOST = 'http://rancher.local:8080/v1' let USERNAME = 'userid' let PASSWORD = 'password' // Attempts to read environment variables to configure the program. if (process.env['CATTLE_ACCESS_KEY']) { USERNAME = process.env['CATTLE_ACCESS_KEY'] } if (process.env['CATTLE_SECRET_KEY']) { PASSWORD = process.env['CATTLE_SECRET_KEY'] } if (process.env['CATTLE_URL']) { HOST = process.env['CATTLE_URL'] } if (process.env['RANCHER_ACCESS_KEY']) { USERNAME = process.env['RANCHER_ACCESS_KEY'] } if (process.env['RANCHER_SECRET_KEY']) { PASSWORD = process.env['RANCHER_SECRET_KEY'] } if (process.env['RANCHER_URL']) { HOST = process.env['RANCHER_URL'] } if (process.env['SSL_VERIFY']) { if ((process.env['SSL_VERIFY'].lower() === 'false')) { process.env['SSL_VERIFY'] = false } } if (HOST && HOST.search(/\/v1$/) < 0) { HOST = (`${HOST}/v1`) } // HTTP async function getRequest (url, options = {}) { try { return await requests.get(url, {'auth': {username: USERNAME, password: PASSWORD}}) } catch (err) { if (!options.silentError === true) { console.log(err, err.message) } return } } async function postRequest (url, data = '') { try { if (data) { return await requests.post(url, data, {'auth': {username: USERNAME, password: PASSWORD}}) } else { return await requests.post(url, {}, {'auth': {username: USERNAME, password: PASSWORD}}) } } catch (err) { return console.log(err, err.message) } } async function deleteRequest (url, data = '') { try { if (data) { return await requests.delete(url, { 'data': JSON.stringify(data), 'auth': {username: USERNAME, password: PASSWORD} }) } else { return await requests.delete(url, {'auth': {username: USERNAME, password: PASSWORD}}) } } catch (err) { return console.log(err, err.message) } } // Websocket function ws (url) { const ws = new WebSocket(url) return new Promise((resolve, reject) => { ws.on('message', function incoming (data) { return resolve(data) }) }) } // Helper function printJson (data) { console.log(util.inspect(data)) } async function sleep (seconds) { return new Promise(resolve => setTimeout(resolve, seconds * 1000)) } // // Query the service configuration. // yargs.command('query [service_id]', 'The ID of the service to read (optional)', () => { }, (argv) => { query(argv.service_id) }) async function query (serviceId = '') { /* Retrieves the service information. If you don't specify an ID, data for all services will be retrieved. */ const r = await getRequest(HOST + URL_SERVICE + serviceId) printJson(r.data) } // // Wait for Rancher to come up // yargs.command('wait_for_rancher [options]', 'Wait for Rancher to start with an active project', (yargs) => { yargs.positional('timeout', {describe: 'How many seconds to wait until waiting fails'}) }, async (argv) => { console.log(await waitForRancher(argv.timeout)) }) async function waitForRancher (timeout = 120) { let sleepCount = 0 let firstProjecctState, r while ((firstProjecctState !== 'active') && (sleepCount < (timeout / 2))) { console.log('Waiting for rancher to start...') await sleep(2) r = await getRequest(HOST + URL_ENVIRONMENT, {silentError: true}) if (r && r.data && r.data.data && r.data.data[0] && r.data.data[0].state) { firstProjecctState = r.data.data[0].state console.log('First projects state is:' + firstProjecctState) } sleepCount += 1 } return 'rancher started' } // // Converts a service name into an ID // yargs.command('id_of <name> [options]', 'Converts a service name into an ID', (yargs) => { yargs.positional('name', {describe: 'The name of the service to lookup.'}) .positional('newest', {describe: 'From list of IDs, return newest (optional)'}) }, async (argv) => { console.log(await idOf(argv.name, argv.newest)) }) async function idOf (name = '', newest = false) { const response = await getRequest(`${HOST}/services?name=${name}`) const service = response.data if (newest) { return service.data[service.data.length - 1].id } return service.data[0].id } // // Converts a environment name into an ID // yargs.command('id_of_env <name>', 'Converts a environment name into an ID', (yargs) => { yargs.positional('name', {describe: 'The name of the environment to lookup'}) }, async (argv) => { console.log(await idOfEnv(argv.name)) }) async function idOfEnv (name = '') { const environment = await getRequest(`${HOST}/project?name=${name}`) return environment.data['data'][0]['id'] } // // Start containers within a service (e.g. for Start Once containers). // yargs.command('start_containers <service_id>', 'Start containers within a service (e.g. for Start Once containers).', (yargs) => { yargs.positional('service_id', {describe: 'The ID of the service to start the containers of.'}) }, async (argv) => { await startContainers(argv.service_id) }) function startContainers (serviceId) { startService(serviceId) } // // Start containers within a service (e.g. for Start Once containers). // yargs.command('start_service <service_id>', 'Start containers within a service (e.g. for Start Once containers).', (yargs) => { yargs.positional('service_id', {describe: 'The ID of the service to start the containers of.'}) }, async (argv) => { await startService(argv.service_id) }) async function startService (serviceId) { const containers = await getRequest(`${(HOST + URL_SERVICE) + serviceId}/instances`).data containers.forEach(async (container) => { const startUrl = container.actions.start console.log(`Starting container ${container['name']} with url ${startUrl}`) await postRequest(startUrl, '') }) } // // Stop containers within a service. // yargs.command('stop_service <service_id>', 'Stop containers within a service.', (yargs) => { yargs.positional('service_id', {describe: 'The ID of the service to stop the containers of.'}) }, async (argv) => { await stopService(argv.service_id) }) async function stopService (serviceId) { const containers = await getRequest((`${(HOST + URL_SERVICE) + serviceId}/instances`)).data containers.forEach(async (container) => { const stopUrl = container['actions']['stop'] console.log(`Stopping container ${container['name']} with url ${stopUrl}`) await postRequest(stopUrl, '') }) } // // Restart containers within a service // yargs.command('restart_service <service_id>', 'Restart containers within a service.', (yargs) => { yargs.positional('service_id', {describe: 'The ID of the service to restart the containers of.'}) }, async (argv) => { await restartService(argv.service_id) }) async function restartService (serviceId) { const containers = await getRequest((`${(HOST + URL_SERVICE) + serviceId}/instances`)).data containers.forEach(async (container) => { const restartUrl = container['actions']['restart'] console.log((`Restarting container: ${container['name']}`)) await postRequest(restartUrl) }) } // // Upgrades the service. // yargs.command('upgrade <service_id> [options]', 'Upgrades the service.', (yargs) => { yargs.positional('service_id', {describe: 'The ID of the service to upgrade.'}) .positional('complete_previous', {describe: 'If set and the service was previously upgraded but the upgrade wasn\'t completed, it will be first marked as Finished and then the upgrade will occur.'}) .positional('imageUuid', {describe: 'If set the config will be overwritten to use new image. Don\'t forget Rancher Formatting \'docker:<Imagename>:tag\''}) .positional('auto_complete', {describe: 'Set this to automatically \'finish upgrade\' once upgrade is complete'}) .positional('replace_env_name', {describe: 'The name of an environment variable to be changed in the launch config (requires replace_env_value).'}) .positional('replace_env_value', {describe: 'The value of the environment variable to be replaced (requires replace_env_name).'}) .positional('timeout', {describe: 'How many seconds to wait until an upgrade fails'}) }, async (argv) => { await upgrade(argv.service_id, argv.start_first, argv.complete_previous, argv.imageUuid, argv.auto_complete, argv.batch_size, argv.interval_millis, argv.replace_env_name, argv.replace_env_value, argv.timeout) }) async function upgrade (serviceId, startFirst = true, completePrevious = false, imageUuid = null, autoComplete = false, batchSize = 1, intervalMillis = 10000, replaceEnvName = null, replaceEnvValue = null, timeout = 60) { /* Upgrades a service Performs a service upgrade, keeping the same configuration, but otherwise pulling new image as needed and starting new containers, dropping the old ones. */ let currentServiceConfig, r, sleepCount, upgradedSleepCount const upgradeStrategy = { 'inServiceStrategy': { 'batchSize': 1, 'intervalMillis': 10000, 'startFirst': true, 'launchConfig': {}, 'secondaryLaunchConfigs': [] } } upgradeStrategy.inServiceStrategy.batchSize = batchSize upgradeStrategy.inServiceStrategy.intervalMillis = intervalMillis if (startFirst) { upgradeStrategy.inServiceStrategy.startFirst = 'true' } else { upgradeStrategy.inServiceStrategy.startFirst = 'false' } r = await getRequest(HOST + URL_SERVICE + serviceId) currentServiceConfig = r.data if (completePrevious && (currentServiceConfig.state === 'upgraded')) { console.log('Previous service upgrade wasn\'t completed, completing it now...') await postRequest(`${(HOST + URL_SERVICE) + serviceId}?action=finishupgrade`, '') r = await getRequest(HOST + URL_SERVICE + serviceId) currentServiceConfig = r.data sleepCount = 0 while ((currentServiceConfig['state'] !== 'active') && (sleepCount < (timeout / 2))) { console.log('Waiting for upgrade to finish...') await sleep(2) r = await getRequest(HOST + URL_SERVICE + serviceId) currentServiceConfig = r.data sleepCount += 1 } } if (currentServiceConfig['state'] !== 'active') { console.log('Service cannot be updated due to its current state: ' + currentServiceConfig['state']) process.exit(1) } upgradeStrategy['inServiceStrategy']['launchConfig'] = currentServiceConfig['launchConfig'] if ((replaceEnvName !== null) && (replaceEnvValue !== null)) { console.log(`Replacing environment variable ${replaceEnvName} from ${upgradeStrategy['inServiceStrategy']['launchConfig']['environment'][replaceEnvName]} to ${replaceEnvValue}`) upgradeStrategy['inServiceStrategy']['launchConfig']['environment'][replaceEnvName] = replaceEnvValue } if (imageUuid !== null) { upgradeStrategy['inServiceStrategy']['launchConfig']['imageUuid'] = imageUuid console.log('New Image: ' + upgradeStrategy['inServiceStrategy']['launchConfig']['imageUuid']) } await postRequest(currentServiceConfig['actions']['upgrade'], upgradeStrategy) console.log(`Upgrade of ${currentServiceConfig['name']} service started!`) r = await getRequest(HOST + URL_SERVICE + serviceId) currentServiceConfig = r.data console.log(`Service State '${currentServiceConfig['state']}'`) console.log('Waiting for upgrade to finish...') sleepCount = 0 while ((currentServiceConfig['state'] !== 'upgraded') && (sleepCount < (timeout / 2))) { console.log('.') await sleep(2) r = await getRequest(HOST + URL_SERVICE + serviceId) currentServiceConfig = r.data sleepCount += 1 } if (sleepCount >= (timeout / 2)) { console.log('Upgrading take to much time! Check Rancher UI for more details.') process.exit(1) } else { console.log('Upgraded') } if (autoComplete && (currentServiceConfig['state'] === 'upgraded')) { await postRequest((`${HOST + URL_SERVICE + serviceId}?action=finishupgrade`), '') r = await getRequest(HOST + URL_SERVICE + serviceId) currentServiceConfig = r.data console.log('Auto Finishing Upgrade...') upgradedSleepCount = 0 while ((currentServiceConfig['state'] !== 'active') && (upgradedSleepCount < (timeout / 2))) { console.log('.') await sleep(2) r = await getRequest(HOST + URL_SERVICE + serviceId) currentServiceConfig = r.data upgradedSleepCount += 1 } if ((currentServiceConfig['state'] === 'active')) { console.log('DONE') } else { console.log('Something has gone wrong! Check Rancher UI for more details.') process.exit(1) } } } // // Execute remote command on container. // yargs.command('execute <service_id> [command]', 'Execute remote command on container.', (yargs) => { yargs.positional('service_id', {describe: 'The ID of the service to execute on'}) .positional('command', {describe: 'The command to execute'}) }, async (argv) => { await execute(argv.service_id, argv.command) }) async function execute (serviceId, command) { /* Execute remote command Executes a command on one container of the service you specified. */ const containers = await getRequest((`${(HOST + URL_SERVICE) + serviceId}/instances`))['data'] if ((containers.length <= 0)) { console.log('No container available') process.exit(1) } const executionUrl = containers[0]['actions']['execute'] console.log(`Executing '${command}' on container '${containers[0]['name']}'`) const payload = {'attachStdin': true, 'attachStdout': true, 'command': ['/bin/sh', '-c'], 'tty': true} payload['command'].append(command) const intermediate = await postRequest(executionUrl, payload) const wsToken = intermediate['token'] const wsUrl = (`${intermediate['url']}?token=${wsToken}`) console.log(`> \n${ws(wsUrl)}`) console.log('DONE') } // // Rollback the service. // yargs.command('rollback <service_id> [options]', 'Rollback the service.', (yargs) => { yargs.positional('service_id', {describe: 'The ID of the service to execute on'}) .positional('timeout', {describe: 'How many seconds to wait until an rollback fails'}) }, async (argv) => { await rollback(argv.service_id, argv.timeout) }) async function rollback (serviceId, timeout = 60) { /* Performs a service rollback */ let currentServiceConfig, r, sleepCount r = await getRequest(HOST + URL_SERVICE + serviceId) currentServiceConfig = r.data if ((currentServiceConfig['state'] !== 'upgraded')) { console.log(`Service cannot be updated due to its current state: ${currentServiceConfig['state']}`) process.exit(1) } await postRequest(currentServiceConfig['actions']['rollback'], '') console.log(`Rollback of ${currentServiceConfig['name']} service started!`) r = await getRequest(HOST + URL_SERVICE + serviceId) currentServiceConfig = r.data console.log(`Service State '${currentServiceConfig['state']}.'`) console.log('Waiting for rollback to finish...') sleepCount = 0 while ((currentServiceConfig['state'] !== 'active') && (sleepCount < (timeout / 2))) { console.log('.') await sleep(2) r = await getRequest(HOST + URL_SERVICE + serviceId) currentServiceConfig = r.data sleepCount += 1 } if ((sleepCount >= (timeout / 2))) { console.log('Rolling back take to much time! Check Rancher UI for more details.') process.exit(1) } else { console.log('Rolled back') } } // // Activate a service. // yargs.command('activate <service_id> [options]', 'Activate a service.', (yargs) => { yargs.positional('service_id', {describe: 'The ID of the service to activate.'}) .positional('timeout', {describe: 'How many seconds to wait until an upgrade fails'}) }, async (argv) => { await activate(argv.service_id, argv.timeout) }) async function activate (serviceId, timeout = 60) { /* Activate the containers of a given service. */ let currentServiceConfig, r, sleepCount r = await getRequest(HOST + URL_SERVICE + serviceId) currentServiceConfig = r.data if (currentServiceConfig['state'] !== 'inactive') { console.log('Service cannot be deactivated due to its current state: ' + currentServiceConfig['state']) process.exit(1) } await postRequest(currentServiceConfig['actions']['activate'], '') sleepCount = 0 while ((currentServiceConfig['state'] !== 'active') && (sleepCount < (timeout / 2))) { console.log('Waiting for activation to finish...') await sleep(2) r = await getRequest(HOST + URL_SERVICE + serviceId) currentServiceConfig = r.data sleepCount += 1 } } // // Deactivate a service. yargs.command('deactivate <service_id> [options]', 'Deactivate a service.', (yargs) => { yargs.positional('service_id', {describe: 'The ID of the service to deactivate.'}) .positional('timeout', {describe: 'How many seconds to wait until an upgrade fails'}) }, async (argv) => { await deactivate(argv.service_id, argv.timeout) }) async function deactivate (serviceId, timeout = 60) { /* Stops the containers of a given service. (e.g. for maintenance purposes) */ let currentServiceConfig, r, sleepCount r = await getRequest(HOST + URL_SERVICE + serviceId) currentServiceConfig = r.data if ((currentServiceConfig['state'] !== 'active') && (currentServiceConfig['state'] !== 'updating-active')) { console.log('Service cannot be deactivated due to its current state: ' + currentServiceConfig['state']) process.exit(1) } await postRequest(currentServiceConfig['actions']['deactivate'], '') sleepCount = 0 while ((currentServiceConfig['state'] !== 'inactive') && (sleepCount < (timeout / 2))) { console.log('Waiting for deactivation to finish...') await sleep(2) r = await getRequest(HOST + URL_SERVICE + serviceId) currentServiceConfig = r.data sleepCount += 1 } } // // Deactivate a env. // yargs.command('deactivate_env <environment_id> [options]', 'Deactivate a env.', (yargs) => { yargs.positional('environment_id', {describe: 'The ID of the environment to deactivate.'}) .positional('timeout', {describe: 'How many seconds to wait until an upgrade fails'}) }, async (argv) => { await deactivateEnv(argv.environment_id, argv.timeout) }) async function deactivateEnv (environmentId, timeout = 60) { /* Stops the environment */ let currentEnvironmentConfig, r, sleepCount r = await getRequest(HOST + URL_ENVIRONMENT + environmentId) currentEnvironmentConfig = r.data if (currentEnvironmentConfig['state'] !== 'active') { console.log(`Environment cannot be deactivated due to its current state: ${currentEnvironmentConfig['state']}`) process.exit(1) } await postRequest(currentEnvironmentConfig['actions']['deactivate'], '') sleepCount = 0 while ((currentEnvironmentConfig['state'] !== 'inactive') && (sleepCount < (timeout / 2))) { console.log('Waiting for deactivation to finish...') await sleep(2) r = await getRequest(HOST + URL_ENVIRONMENT + environmentId) currentEnvironmentConfig = r.data sleepCount += 1 } } // // Delete a env. // yargs.command('delete_env <environment_id> [options]', 'Delete a env.', (yargs) => { yargs.positional('environment_id', {describe: 'The ID of the environment to delete.'}) .positional('timeout', {describe: 'How many seconds to wait until an upgrade fails'}) }, async (argv) => { await deleteEnv(argv.environment_id, argv.timeout) }) async function deleteEnv (environmentId, timeout = 60) { /* Stops the environment */ let currentEnvironmentConfig, r, sleepCount r = await getRequest(HOST + URL_ENVIRONMENT + environmentId) currentEnvironmentConfig = r.data if ((currentEnvironmentConfig['state'] !== 'inactive')) { console.log(`Environment cannot be deactivated due to its current state: ${currentEnvironmentConfig['state']}`) process.exit(1) } await deleteRequest(currentEnvironmentConfig['actions']['delete'], '') sleepCount = 0 while ((currentEnvironmentConfig['state'] !== 'removed') && (sleepCount < (timeout / 2))) { console.log('Waiting for delete to finish...') await sleep(2) r = await getRequest(HOST + URL_ENVIRONMENT + environmentId) currentEnvironmentConfig = r.data sleepCount += 1 } } // // Remove a service. // yargs.command('remove <service_id> [options]', 'Deactivate a env.', (yargs) => { yargs.positional('service_id', {describe: 'The ID of the service to remove.'}) .positional('timeout', {describe: 'How many seconds to wait until an upgrade fails'}) }, async (argv) => { await remove(argv.service_id, argv.timeout) }) async function remove (serviceId, timeout = 60) { /* Remove the service */ let currentServiceConfig, r, sleepCount r = await getRequest(HOST + URL_SERVICE + serviceId) currentServiceConfig = r.data if ((currentServiceConfig['state'] !== 'inactive')) { console.log(`Service cannot be removed due to its current state: ${currentServiceConfig['state']}`) process.exit(1) } await postRequest(currentServiceConfig['actions']['remove'], '') sleepCount = 0 while ((currentServiceConfig['state'] !== 'removed') && (sleepCount < (timeout / 2))) { console.log('Waiting for remove to finish...') await sleep(2) r = await getRequest(HOST + URL_SERVICE + serviceId) currentServiceConfig = r.data sleepCount += 1 } } // // Get a service state // // baker.command(state, {name: 'state', default: true, opts: {'service_id': 'The ID of the service to read'}}) yargs.command('state <service_id> [options]', 'Get a service state', (yargs) => { yargs.positional('service_id', {describe: 'The ID of the service to read'}) }, async (argv) => { console.log(await state(argv.service_id)) }) async function state (serviceId = '') { /* Retrieves the service state information. */ const r = await getRequest(HOST + URL_SERVICE + serviceId) return(r.data['state']) } yargs.usage(`$0 <cmd> [args] Define the environment Variables: RANCHER_ACCESS_KEY RANCHER_SECRET_KEY RANCHER_URL optional: SSL_VERIFY=false`) yargs.demandCommand(1, 'You need at least one command before moving on') if (require.main === module) { // Provide commandline-cli if started from commandline yargs.help().argv } else { // Provide exports if included as dependency module.exports = { activate, deactivate, deactivateEnv, deleteEnv, deleteRequest, execute, idOf, idOfEnv, query, remove, restartService, rollback, startContainers, startService, state, stopService, upgrade } }