UNPKG

@vroomlabs/gsdk-deploy

Version:

Google Cloud deployment script for kubernetes clusters using Global Load Balancer

254 lines (204 loc) 14.6 kB
'use strict'; /****************************************************************************** * MIT License * Copyright (c) 2017 https://github.com/vroomlabs * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Created by rogerk on 7/4/17. ******************************************************************************/Object.defineProperty(exports,'__esModule',{value:true});exports.GoogleLoadBalancer=undefined;var _createClass=function(){function defineProperties(target,props){for(var i=0;i<props.length;i++){var descriptor=props[i];descriptor.enumerable=descriptor.enumerable||false;descriptor.configurable=true;if('value'in descriptor)descriptor.writable=true;Object.defineProperty(target,descriptor.key,descriptor)}}return function(Constructor,protoProps,staticProps){if(protoProps)defineProperties(Constructor.prototype,protoProps);if(staticProps)defineProperties(Constructor,staticProps);return Constructor}}(); var _fs=require('fs');var fs=_interopRequireWildcard(_fs); var _pem=require('pem');var pem=_interopRequireWildcard(_pem); var _logger=require('../util/logger'); var _callback=require('../util/callback'); var _templateArg=require('../util/templateArg'); var _googleAsyncTask=require('../google/googleAsyncTask');function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key]}}newObj.default=obj;return newObj}}function _asyncToGenerator(fn){return function(){var gen=fn.apply(this,arguments);return new Promise(function(resolve,reject){function step(key,arg){try{var info=gen[key](arg);var value=info.value}catch(error){reject(error);return}if(info.done){resolve(value)}else{return Promise.resolve(value).then(function(value){step('next',value)},function(err){step('throw',err)})}}return step('next')})}}function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor)){throw new TypeError('Cannot call a class as a function')}}var GoogleLoadBalancer=exports.GoogleLoadBalancer=function(){ /** * @param {GoogleApi} gauth */ function GoogleLoadBalancer(gauth){_classCallCheck(this,GoogleLoadBalancer); this.gauth=gauth; this.wait=new _googleAsyncTask.GoogleAsyncTask(this.gauth); // These methods are used in scripts [this.checkDnsEntry,this.configureBackend,this.selectCertificate].toString(); } /** * @param {{scriptFile: string, scriptName: string, mode: string}} scriptInfo * @param {BackendConfig} backendConfig */_createClass(GoogleLoadBalancer,[{key:'execScript',value:function(){var _ref=_asyncToGenerator(/*#__PURE__*/regeneratorRuntime.mark(function _callee( scriptInfo,backendConfig){var script,state,ix,taskSet,taskKeys,ixt,task,result;return regeneratorRuntime.wrap(function _callee$(_context){while(1){switch(_context.prev=_context.next){case 0: script=JSON.parse(fs.readFileSync(scriptInfo.scriptFile)); script=script[scriptInfo.scriptName];if(!( !script||!Array.isArray(script))){_context.next=4;break}throw( new Error('Unable to locate script: '+scriptInfo.scriptName));case 4: // For delete, play backwards if(scriptInfo.mode==='delete'){ script=script.reverse(); } state={ NAME:backendConfig.name, HOSTNAME:backendConfig.hostname, NODE_PORT:backendConfig.nodePort, SERVICE_NAME:backendConfig.serviceName, LIVENESSPROBE:backendConfig.livenessProbe}; ix=0;case 7:if(!(ix<script.length)){_context.next=24;break} taskSet=script[ix]; taskKeys=Object.keys(taskSet); ixt=0;case 11:if(!(ixt<taskKeys.length)){_context.next=21;break} task=Object.assign({},taskSet[taskKeys[ixt]],{type:taskKeys[ixt],mode:scriptInfo.mode}); if(scriptInfo.mode!=='create'){ delete task.create; delete task.params; }_context.next=16;return( this.execTask((0,_templateArg.replaceInObject)(task,state),backendConfig));case 16:result=_context.sent; if(result&&result.hasOwnProperty(task.returns)) state[taskKeys[ixt].toUpperCase()]=result[task.returns];case 18:ixt++;_context.next=11;break;case 21:ix++;_context.next=7;break;case 24:case'end':return _context.stop();}}},_callee,this)}));function execScript(_x,_x2){return _ref.apply(this,arguments)}return execScript}() /** * * @param {{type, mode, fetch, create, invoke, preserve, params}} task * @param {BackendConfig} backendConfig */},{key:'execTask',value:function(){var _ref2=_asyncToGenerator(/*#__PURE__*/regeneratorRuntime.mark(function _callee2( task,backendConfig){var type,mode,fetch,create,invoke,verb,result,req;return regeneratorRuntime.wrap(function _callee2$(_context2){while(1){switch(_context2.prev=_context2.next){case 0: _logger.logger.verbose('starting compute task:',task); type=task.type,mode=task.mode,fetch=task.fetch,create=task.create,invoke=task.invoke; verb='get';if(!( mode==='delete')){_context2.next=8;break}if(!( task.preserve||!task.fetch)){_context2.next=6;break}return _context2.abrupt('return');case 6: _logger.logger.warn('Removing '+type+' for '+task.fetch[Object.keys(task.fetch)[0]]); verb='delete';case 8: result=null;_context2.prev=9;if(! fetch){_context2.next=15;break} fetch.project=this.gauth.project;_context2.next=14;return( (0,_callback.promisify)(this.gauth.compute[type],verb,fetch));case 14:result=_context2.sent;case 15:_context2.next=23;break;case 17:_context2.prev=17;_context2.t0=_context2['catch'](9);if(!( _context2.t0.code!==404)){_context2.next=22;break} _logger.logger.error('Unable to get '+type+' matching: '+JSON.stringify(fetch));throw _context2.t0;case 22: _logger.logger.verbose(type+' not found',fetch);case 23:if(!( mode==='delete'&&result&&!task.preserve)){_context2.next=27;break}_context2.next=26;return( this.wait.completeTask(result));case 26: result=null;case 27:if(!( mode!=='delete'&&invoke)){_context2.next=31;break}_context2.next=30;return( this[invoke](backendConfig,task.params,result));case 30:result=_context2.sent;case 31:if(!( mode==='create'&&!result&&create)){_context2.next=42;break} _logger.logger.warn('Creating '+type+'...',fetch); req={project:this.gauth.project,resource:create};_context2.next=36;return( (0,_callback.promisify)(this.gauth.compute[type],'insert',req));case 36:result=_context2.sent;_context2.next=39;return( this.wait.completeTask(result));case 39:_context2.next=41;return( (0,_callback.promisify)(this.gauth.compute[type],'get',fetch));case 41:result=_context2.sent;case 42: _logger.logger.silly('compute task result',{type:type,result:result});return _context2.abrupt('return', result);case 44:case'end':return _context2.stop();}}},_callee2,this,[[9,17]])}));function execTask(_x3,_x4){return _ref2.apply(this,arguments)}return execTask}() /** * @param {BackendConfig} backendConfig * @param {{host, address}} params */},{key:'checkDnsEntry',value:function(){var _ref3=_asyncToGenerator(/*#__PURE__*/regeneratorRuntime.mark(function _callee3( backendConfig,params){var actual;return regeneratorRuntime.wrap(function _callee3$(_context3){while(1){switch(_context3.prev=_context3.next){case 0: actual=void 0;_context3.prev=1;_context3.next=4;return( (0,_callback.promisify)(require('dns'),'lookup',params.host,{family:4}));case 4:actual=_context3.sent;_context3.next=11;break;case 7:_context3.prev=7;_context3.t0=_context3['catch'](1); actual='['+(_context3.t0.code||_context3.t0.message)+']'; _logger.logger.silly('dns lookup error: '+params.host,_context3.t0);case 11: if(actual!==params.address){ _logger.logger.warn('Missing DNS record: '+params.host+' = '+params.address); }case 12:case'end':return _context3.stop();}}},_callee3,this,[[1,7]])}));function checkDnsEntry(_x5,_x6){return _ref3.apply(this,arguments)}return checkDnsEntry}() /** * Create the backend service description * @param {BackendConfig} backendConfig * @param {{healthCheck: string}} params */},{key:'configureBackend',value:function(){var _ref4=_asyncToGenerator(/*#__PURE__*/regeneratorRuntime.mark(function _callee4( backendConfig,params){var fetch,backend,create,op,update,_op;return regeneratorRuntime.wrap(function _callee4$(_context4){while(1){switch(_context4.prev=_context4.next){case 0: backendConfig.healthCheck=params.healthCheck; fetch={project:this.gauth.project,backendService:backendConfig.name}; backend=null;_context4.prev=3;_context4.next=6;return( (0,_callback.promisify)(this.gauth.compute.backendServices,'get',fetch));case 6:backend=_context4.sent;_context4.next=13;break;case 9:_context4.prev=9;_context4.t0=_context4['catch'](3);if(!( _context4.t0.code!==404)){_context4.next=13;break}throw _context4.t0;case 13:if( backend){_context4.next=26;break} create={ project:this.gauth.project, resource:backendConfig.getBackendConfig()}; _logger.logger.warn('Creating service backend '+backendConfig.name+'...');_context4.next=18;return( (0,_callback.promisify)(this.gauth.compute.backendServices,'insert',create));case 18:op=_context4.sent;_context4.next=21;return( this.wait.completeTask(op));case 21:_context4.next=23;return( (0,_callback.promisify)(this.gauth.compute.backendServices,'get',fetch));case 23:backend=_context4.sent;_context4.next=37;break;case 26: update={ project:this.gauth.project, backendService:backendConfig.name, resource:backendConfig.getBackendUpdate(backend)};if(!( update.resource!==null)){_context4.next=37;break} _logger.logger.warn('Updating service backend '+backendConfig.name+'...');_context4.next=31;return( (0,_callback.promisify)(this.gauth.compute.backendServices,'update',update));case 31:_op=_context4.sent;_context4.next=34;return( this.wait.completeTask(_op));case 34:_context4.next=36;return( (0,_callback.promisify)(this.gauth.compute.backendServices,'get',fetch));case 36:backend=_context4.sent;case 37:return _context4.abrupt('return', backend);case 38:case'end':return _context4.stop();}}},_callee4,this,[[3,9]])}));function configureBackend(_x7,_x8){return _ref4.apply(this,arguments)}return configureBackend}() /** * Finds the certifcate that best matches a hostname * @param {BackendConfig} backendConfig */},{key:'selectCertificate',value:function(){var _ref5=_asyncToGenerator(/*#__PURE__*/regeneratorRuntime.mark(function _callee5( backendConfig){var dnsmap,certKey,cert;return regeneratorRuntime.wrap(function _callee5$(_context5){while(1){switch(_context5.prev=_context5.next){case 0:_context5.next=2;return( this.loadCertificateMap());case 2:dnsmap=_context5.sent; certKey=backendConfig.hostname.toLowerCase(); if(!dnsmap[certKey]){ certKey=certKey.split('.'); certKey[0]='*'; certKey=certKey.join('.'); }if( dnsmap[certKey]){_context5.next=7;break}throw( new Error('Unable to locate a certificate for '+backendConfig.hostname+'.'));case 7: cert=Object.assign({},dnsmap[certKey]); cert.domain=certKey; _logger.logger.verbose('Using certifcate '+certKey+' for '+backendConfig.hostname+'.');return _context5.abrupt('return', cert);case 11:case'end':return _context5.stop();}}},_callee5,this)}));function selectCertificate(_x9){return _ref5.apply(this,arguments)}return selectCertificate}() /** * Loads the available ssl certs from Networking->Load Balancing->Advanced->Certificates */},{key:'loadCertificateMap',value:function(){var _ref6=_asyncToGenerator(/*#__PURE__*/regeneratorRuntime.mark(function _callee6(){var req,coll,parsers,dnsMap,certificates;return regeneratorRuntime.wrap(function _callee6$(_context6){while(1){switch(_context6.prev=_context6.next){case 0: req={project:this.gauth.project}; _logger.logger.verbose('Fetching certificates list...');_context6.next=4;return( GoogleLoadBalancer.fetchList(req,this.gauth.compute.sslCertificates));case 4:coll=_context6.sent; _logger.logger.silly('Found '+coll.length+' certificate(s).'); parsers=coll.map(function(cert){ return new Promise(function(accept,reject){ pem.readCertificateInfo(cert.certificate,function(err,data){ if(err)return reject(err); var names=[data.commonName]; if(data.san&&Array.isArray(data.san.dns)){ names=data.san.dns; } return accept({name:cert.name,selfLink:cert.selfLink,dns:names}); }); }); }); dnsMap={};_context6.next=10;return( Promise.all(parsers));case 10:certificates=_context6.sent; certificates.forEach(function(e){ _logger.logger.silly('Found cert '+e.name+' for domains '+e.dns.join(',')); e.dns.forEach(function(dns){ dnsMap[dns]={name:e.name,selfLink:e.selfLink}; }); }); _logger.logger.verbose('Found '+certificates.length+' certificates.',dnsMap);return _context6.abrupt('return', dnsMap);case 14:case'end':return _context6.stop();}}},_callee6,this)}));function loadCertificateMap(){return _ref6.apply(this,arguments)}return loadCertificateMap}() /** * @param {{project: string}} request - the request object for the collection * @param {{list: function}} coll - the compute.* member containing the list method */}],[{key:'fetchList',value:function(){var _ref7=_asyncToGenerator(/*#__PURE__*/regeneratorRuntime.mark(function _callee7( request,coll){var found,page;return regeneratorRuntime.wrap(function _callee7$(_context7){while(1){switch(_context7.prev=_context7.next){case 0: found=[]; page={nextPageToken:'first'};case 2:if(!( page&&page.nextPageToken)){_context7.next=11;break} _logger.logger.silly('fetching list, page = '+page.nextPageToken);_context7.next=6;return( (0,_callback.promisify)(coll,'list',request));case 6:page=_context7.sent; found=found.concat(page.items||[]); request.pageToken=page.nextPageToken;_context7.next=2;break;case 11:return _context7.abrupt('return', found);case 12:case'end':return _context7.stop();}}},_callee7,this)}));function fetchList(_x10,_x11){return _ref7.apply(this,arguments)}return fetchList}()}]);return GoogleLoadBalancer}();