UNPKG

@vroomlabs/gsdk-deploy

Version:

Google Cloud deployment script for kubernetes clusters using Global Load Balancer

281 lines (205 loc) 14.3 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/3/17. ******************************************************************************/Object.defineProperty(exports,'__esModule',{value:true});exports.GoogleKubernetes=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 _path=require('path');var path=_interopRequireWildcard(_path); var _logger=require('../util/logger'); var _shell=require('../util/shell');var shell=_interopRequireWildcard(_shell); var _callback=require('../util/callback'); var _templateArg=require('../util/templateArg'); var _googleAsyncTask=require('../google/googleAsyncTask'); var _kubernetesControl=require('./kubernetesControl');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 GoogleKubernetes=exports.GoogleKubernetes=function(){ /** * @param {GoogleApi} gauth * @param {Configuration} config */ function GoogleKubernetes(gauth,config){_classCallCheck(this,GoogleKubernetes); this.gauth=gauth; this.config=config; } /** * @param {string} name - name of the service * @param {string} dockerImage * @param {{name: string, version: string, grpc: boolean, hasApi: boolean}} endpointInfo * @param {object} clusters - map from result of getClusters */_createClass(GoogleKubernetes,[{key:'deployAll',value:function(){var _ref=_asyncToGenerator(/*#__PURE__*/regeneratorRuntime.mark(function _callee( name,dockerImage,endpointInfo,clusters){var clusterNames,deployments,serviceConfig,ix,cName,cluster,kubectl;return regeneratorRuntime.wrap(function _callee$(_context){while(1){switch(_context.prev=_context.next){case 0: clusterNames=Object.keys(clusters); deployments=[]; serviceConfig={ name:name, image:dockerImage, endpointName:endpointInfo.name, endpointVersion:endpointInfo.version, protocol:endpointInfo.grpc?'grpc':'http', proxyImage:this.config.current.proxyImage||(endpointInfo.hasApi? 'gcr.io/endpoints-release/endpoints-runtime:1': 'tdalabs/nginx-ssl-proxy:latest')}; ix=0;case 4:if(!(ix<clusterNames.length)){_context.next=20;break} cName=clusterNames[ix]; cluster=clusters[cName];_context.next=9;return( this.getKubeController(cluster));case 9:kubectl=_context.sent;_context.t0= deployments;_context.next=13;return kubectl.deployImage(serviceConfig);case 13:_context.t1=_context.sent;_context.t0.push.call(_context.t0,_context.t1);_context.next=17;return( this.configureNamedPort(cluster));case 17:ix++;_context.next=4;break;case 20:return _context.abrupt('return', deployments);case 21:case'end':return _context.stop();}}},_callee,this)}));function deployAll(_x,_x2,_x3,_x4){return _ref.apply(this,arguments)}return deployAll}() /** * Returns an authenticated KubernetesControl object * @param {{name: string, zone: string}} cluster */},{key:'getKubeController',value:function(){var _ref2=_asyncToGenerator(/*#__PURE__*/regeneratorRuntime.mark(function _callee2( cluster){return regeneratorRuntime.wrap(function _callee2$(_context2){while(1){switch(_context2.prev=_context2.next){case 0: _logger.logger.info('Connecting to cluster '+cluster.name);_context2.next=3;return( shell.exec(this.gauth.gcloud+' container clusters get-credentials --quiet '+('--project '+ this.gauth.project+' --zone '+cluster.zone+' '+cluster.name),{direct:'verbose'}));case 3:return _context2.abrupt('return', new _kubernetesControl.KubernetesControl(this.config,cluster));case 4:case'end':return _context2.stop();}}},_callee2,this)}));function getKubeController(_x5){return _ref2.apply(this,arguments)}return getKubeController}() /** * @param {boolean} createMissing */},{key:'getClusters',value:function(){var _ref3=_asyncToGenerator(/*#__PURE__*/regeneratorRuntime.mark(function _callee3( createMissing){var self,existing,clusters,mapCluster,missing,todo,results;return regeneratorRuntime.wrap(function _callee3$(_context3){while(1){switch(_context3.prev=_context3.next){case 0: self=this;_context3.next=3;return( this.getMatchingClusters());case 3:existing=_context3.sent; clusters={}; mapCluster=function mapCluster(c){ clusters[c.name.toLowerCase()]={ name:c.name, network:c.network, zone:c.zone, currentNodeCount:c.currentNodeCount, instanceGroups:c.instanceGroupUrls.map(function(url){return url.replace(/\/instanceGroupManagers\//g,'/instanceGroups/')})}; }; existing.forEach(mapCluster); missing=this.config.current.clusters.filter(function(c){return!clusters[c]});if(! missing.length){_context3.next=18;break} _logger.logger.verbose('Some clusters are missing.',{existing:Object.keys(clusters),missing:missing});if(! createMissing){_context3.next=16;break} todo=missing.map(function(name){return self.createCluster(name)});_context3.next=14;return( Promise.all(todo));case 14:results=_context3.sent; results.forEach(mapCluster);case 16:_context3.next=19;break;case 18: _logger.logger.verbose('Clusters found:',{existing:Object.keys(clusters)});case 19: _logger.logger.silly('clusters loaded:',{clusters:Object.keys(clusters),expected:this.config.current.clusters}); Object.assign(this.config.clusters,clusters);return _context3.abrupt('return', clusters);case 22:case'end':return _context3.stop();}}},_callee3,this)}));function getClusters(_x6){return _ref3.apply(this,arguments)}return getClusters}()},{key:'getTemplate',value:function getTemplate( name,zone){ var text=fs.readFileSync(this.config.current.clusterTemplate).toString(); var values={ CLUSTER_NAME:name, NETWORK_NAME:this.config.current.network, ZONE_NAME:zone, CLUSTER_SIZE:this.config.current.replicas, MACHINE_TYPE:this.config.current.machineType, DISK_SIZE:this.config.current.diskSizeGb}; text=(0,_templateArg.replaceInText)(text,values); return JSON.parse(text); }},{key:'createCluster',value:function(){var _ref4=_asyncToGenerator(/*#__PURE__*/regeneratorRuntime.mark(function _callee4( name){var waiting,req,self,opcreate,opupdate;return regeneratorRuntime.wrap(function _callee4$(_context4){while(1){switch(_context4.prev=_context4.next){case 0: waiting=new _googleAsyncTask.GoogleAsyncTask(this.gauth); req={ projectId:this.config.project, zone:this.regionZoneByName(name).zone||'-'}; req.resource=this.getTemplate(name,req.zone); _logger.logger.silly('creating cluster '+name,req); self=this; _logger.logger.warn('Creating cluster '+name+'...');_context4.next=8;return( (0,_callback.promisify)(self.gauth.container.projects.zones.clusters,'create',req));case 8:opcreate=_context4.sent;_context4.next=11;return( waiting.completeTask(opcreate));case 11: // Change to define management policy req.clusterId=name; req.nodePoolId='default-pool'; req.resource={ management:{ autoUpgrade:this.config.current.autoUpgrade, autoRepair:this.config.current.autoRepair}}; _logger.logger.info('Updating management policy '+name+'...');_context4.next=17;return( (0,_callback.promisify)(self.gauth.container.projects.zones.clusters.nodePools,'setManagement',req));case 17:opupdate=_context4.sent;_context4.next=20;return( waiting.completeTask(opupdate));case 20:return _context4.abrupt('return', this.getClusterByName(name));case 21:case'end':return _context4.stop();}}},_callee4,this)}));function createCluster(_x7){return _ref4.apply(this,arguments)}return createCluster}() /** * Filters the existing clusters by those that match the configuration */},{key:'getMatchingClusters',value:function(){var _ref5=_asyncToGenerator(/*#__PURE__*/regeneratorRuntime.mark(function _callee5(){var clusters,filter;return regeneratorRuntime.wrap(function _callee5$(_context5){while(1){switch(_context5.prev=_context5.next){case 0:_context5.next=2;return( this.getAllExistingClusters());case 2:clusters=_context5.sent; filter={}; this.config.current.clusters.forEach(function(cname){filter[cname]=true});return _context5.abrupt('return', clusters.filter(function(c){return filter[c.name.toLowerCase()]}));case 6:case'end':return _context5.stop();}}},_callee5,this)}));function getMatchingClusters(){return _ref5.apply(this,arguments)}return getMatchingClusters}() /** * Returns all clusters that exist in the project */},{key:'getAllExistingClusters',value:function(){var _ref6=_asyncToGenerator(/*#__PURE__*/regeneratorRuntime.mark(function _callee6(){var req,result;return regeneratorRuntime.wrap(function _callee6$(_context6){while(1){switch(_context6.prev=_context6.next){case 0: req={ projectId:this.config.project, zone:'-'};_context6.next=3;return( (0,_callback.promisify)(this.gauth.container.projects.zones.clusters,'list',req));case 3:result=_context6.sent;return _context6.abrupt('return', result.clusters||[]);case 5:case'end':return _context6.stop();}}},_callee6,this)}));function getAllExistingClusters(){return _ref6.apply(this,arguments)}return getAllExistingClusters}() /** * Returns a given cluster by name */},{key:'getClusterByName',value:function(){var _ref7=_asyncToGenerator(/*#__PURE__*/regeneratorRuntime.mark(function _callee7( name){var req;return regeneratorRuntime.wrap(function _callee7$(_context7){while(1){switch(_context7.prev=_context7.next){case 0: req={ projectId:this.config.project, zone:this.regionZoneByName(name).zone||'-', clusterId:name};return _context7.abrupt('return', (0,_callback.promisify)(this.gauth.container.projects.zones.clusters,'get',req));case 2:case'end':return _context7.stop();}}},_callee7,this)}));function getClusterByName(_x8){return _ref7.apply(this,arguments)}return getClusterByName}() /** * @param {{zone: string, instanceGroups: string[]}} cluster * @param {{name: string, port: number}} [port] */},{key:'configureNamedPort',value:function(){var _ref8=_asyncToGenerator(/*#__PURE__*/regeneratorRuntime.mark(function _callee8( cluster,port){var self,ix,zoneName,grp,uriParts,pix,fetch,groupInfo,namedPorts,req;return regeneratorRuntime.wrap(function _callee8$(_context8){while(1){switch(_context8.prev=_context8.next){case 0: self=this; if(!port){ port={ name:this.config.name+'-port', port:this.config.getNodePort()}; } ix=0;case 3:if(!(ix<cluster.instanceGroups.length)){_context8.next=29;break} zoneName=cluster.zone; //.../v1/projects/tda-win-servers/zones/us-central1-b/instanceGroups/ grp=cluster.instanceGroups[ix]; uriParts=grp.split('/'); pix=0;case 8:if(!(pix<uriParts.length-1)){_context8.next=15;break}if(!( uriParts[pix]==='zones')){_context8.next=12;break} zoneName=uriParts[pix+1];return _context8.abrupt('break',15);case 12:pix++;_context8.next=8;break;case 15: fetch={ project:this.config.project, zone:zoneName, instanceGroup:path.basename(grp)};_context8.next=18;return( (0,_callback.promisify)(self.gauth.compute.instanceGroups,'get',fetch));case 18:groupInfo=_context8.sent;if(!( groupInfo.namedPorts&& groupInfo.namedPorts.filter(function(p){return p.name===port.name&&p.port===port.port}).length>0)){_context8.next=21;break}return _context8.abrupt('continue',26);case 21: _logger.logger.info('Updating named port on '+fetch.instanceGroup+' with '+port.name+' = '+port.port); namedPorts=(groupInfo.namedPorts||[]).filter(function(p){return p.name!==port.name}).concat(port); req={ project:this.config.project, zone:zoneName, instanceGroup:path.basename(grp), resource:{ namedPorts:namedPorts, fingerprint:groupInfo.fingerprint}};_context8.next=26;return( (0,_callback.promisify)(self.gauth.compute.instanceGroups,'setNamedPorts',req));case 26:ix++;_context8.next=3;break;case 29:case'end':return _context8.stop();}}},_callee8,this)}));function configureNamedPort(_x9,_x10){return _ref8.apply(this,arguments)}return configureNamedPort}() /** * @param {string} name * @returns {{region: string, zone: string}} */},{key:'regionZoneByName',value:function regionZoneByName( name){ var m=name.match(/([a-z]+-(north|south|east|west|central)+\d{1,2})-[a-z]/); return{ region:m?m[1]:null, zone:m?m[0]:null}; }}]);return GoogleKubernetes}();