@vroomlabs/gsdk-deploy
Version:
Google Cloud deployment script for kubernetes clusters using Global Load Balancer
281 lines (205 loc) • 14.3 kB
JavaScript
'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}();