From 11aab6e031cd9d8c8d2a0c786a2c150ab08f5e60 Mon Sep 17 00:00:00 2001 From: Paul Trappitt Date: Sat, 13 Feb 2016 13:28:27 +0800 Subject: [PATCH] completed and tested app.js --- README.md | 115 +++++++++++---- app.js | 392 +++++++++++++++++++++++++++------------------------ humps | 1 + package.json | 3 +- qtest.js | 62 -------- taskDef.json | 15 ++ 6 files changed, 311 insertions(+), 277 deletions(-) create mode 160000 humps delete mode 100644 qtest.js create mode 100644 taskDef.json diff --git a/README.md b/README.md index cba37d1..6bec244 100644 --- a/README.md +++ b/README.md @@ -4,19 +4,19 @@ [![Coverage Status](https://aircover.co/badges/drone-plugins/drone-ecs/coverage.svg)](https://aircover.co/drone-plugins/drone-ecs) [![](https://badge.imagelayers.io/plugins/drone-ecs:latest.svg)](https://imagelayers.io/?images=plugins/drone-ecs:latest 'Get your own badge on imagelayers.io') -Drone plugin to deploy or update a project on AWS ECS. This has been model on the original drone-ecs plugin written in go, thanks to the drone team for their efforts and open sourcing their sofwtare. +Drone plugin to deploy or update a project on AWS ECS. This has been model on the original drone-ecs plugin written in go, thanks to the drone team for their efforts and open sourcing their sofwtare. The original plugin while fully functional had not been designed or intended to be used with environments setup using cloud formation or complex ecs clusters with multiple servcies, tasks and multiple containers in task definitions. This plugin is intended to preserve existing configurations and handle the dynamic nature of cloudformation resources by using wildcards instead of fixed names. You can still use this plugin and the added config features with fixed names as well. It works exactly the same for both setups. -Another challenge with cloudformation is the management of enviornment variables. The are scenarios where environment variables for task deifnitions that point to other resources are set dynamically when the cloudformation script is run. Having to maintain these variable values manually in the drone.yml is not practical. +Another challenge with cloudformation is the management of enviornment variables. The are scenarios where environment variables for task deifnitions that point to other resources are set dynamically when the cloudformation script is run. Having to maintain these variable values manually in the drone.yml is not practical. -This plugin handles this situation by first retrieving the existing task definitions and using them as a template for the new definition. This way the configuration is preserved from the initial cloudformation deployment through subsequent drone deployments. In order to update the configuration, the plugin provides the ability to make changes to explicit parts of the configuration as required through the drone.yml file, while leaving the remaining configuration in tact. +This plugin handles this situation by first retrieving the existing task definitions and using them as a template for the new definition. This way the configuration is preserved from the initial cloudformation deployment through subsequent drone deployments. In order to update the configuration, the plugin provides the ability to make changes to explicit parts of the configuration as required through the drone.yml file, while leaving the remaining configuration in tact. Full examples of all functionality are available below. -This plugin hasn't been extensively tested. We do have a fairly complex ecs environment which covers all the scenarios the plugin can handle. Over the coming months more and more testing will take place. +This plugin hasn't been extensively tested. We do have a fairly complex ecs environment which covers all the scenarios the plugin can handle. Over the coming months more and more testing will take place. -If you find it useful and want to contribute, pull requests are more than welcome. I'll do my best to address any issues as quickly as possible, but please bear in mind, we built this to address a pressing need in our business. Our core business project is our current focus, so for larger issues or feature requests, unless they are impacting us or of value to our project regrettably at the present time I can't give them much priority. +If you find it useful and want to contribute, pull requests are more than welcome. I'll do my best to address any issues as quickly as possible, but please bear in mind, we built this to address a pressing need in our business. Our core business project is our current focus, so for larger issues or feature requests, unless they are impacting us or of value to our project regrettably at the present time I can't give them much priority. ## Using the plugin in drone.yml @@ -27,14 +27,14 @@ deploy: ecs: image: fridaystreet/drone-ecs-node //path to plugin repo region: ap-southeast-2 //aws region to use, currently only supports single region - access_key: $$AWS_KEY //aws access key + access_key: $$AWS_KEY //aws access key secret_key: $$AWS_SECRET //aws secret key - image_name: registry.mydomain.com/dashboard //name of image without tag. + image_name: registry.mydomain.com/dashboard //name of image without tag. image_tag: "1.0.$$BUILD_NUMBER" //image tag cluster: Production-DashboardCluster //base cluster name / wildcard family: Production-DashboardTaskDefinition //base family name / wildcard service: Production-DashboardService //base service name / wildcard - constainer_name: dashboard //the name of the container in the definition that uses this image + constainer_name: dashboard //the name of the container in the definition that uses this image allow_multiple_clusters: false //update services on multiple clusters if matched allow_multiple_services: false //update multiple services on a clusters if matched log_level: 'debug' //logging level to output @@ -42,19 +42,19 @@ deploy: ``` ### Settings explained -Note - The following settings from drone-ecs are not used in drone-ecs-node. +Note - The following settings from drone-ecs are not used in drone-ecs-node. *port_mappings *memory - *environment_variables + *environment_variables *NAME=VALUE -These settings are now handle in an ecs task definition configuration object. See below for details. +These settings are now handle in an ecs task definition configuration object. See below for details. -Note - Any settings that aren't listed below, operate in exactly the same way as drone-ecs http://readme.drone.io/plugins/ecs +Note - Any settings that aren't listed below, operate in exactly the same way as drone-ecs http://readme.drone.io/plugins/ecs ####cluster -The cluster, service & family settings all do indexOf matches on the full Arn of the relative resource. +The cluster, service & family settings all do indexOf matches on the full Arn of the relative resource. This means that is you have a cluster with an Arn of : @@ -80,19 +80,19 @@ Again a good example is if you have a environment where instead of using differe The family parameter operates as a wildcard as well, but it is used to match to task definitions. When the plugin matches a cluster and service, it retrieves all the current tasks within that service. From these tasks it derives the currently active task definitions. These are matched / filtered by this setting to determine which task definitions to update. ####constainer_name -The container name is required in order to determine which conatiner within the task definition to update the image name for this build. This setting is intended to accomodate definitions with multiple containers. +The container name is required in order to determine which conatiner within the task definition to update the image name for this build. This setting is intended to accomodate definitions with multiple containers. Note - It is a non-case sensitive exact match on the name. It does not support wildcards. ####allow_multiple_clusters (optional) -This defaults to false as a safety precuation. If set to true it will allow matched definitions in multiple clusters to be updated at once. +This defaults to false as a safety precuation. If set to true it will allow matched definitions in multiple clusters to be updated at once. ####allow_multiple_services (optional) -This defaults to false as a safety precuation. If set to true it will allow matched definitions in multiple services in the same cluster to be updated at once. +This defaults to false as a safety precuation. If set to true it will allow matched definitions in multiple services in the same cluster to be updated at once. ####task_definition (optional) -The task_definition setting lets you specify a full or part ECS JSON formatted task definition to override one or all settings within the existing task defintion. +The task_definition setting lets you specify a full or part ECS JSON formatted task definition to override one or all settings within the existing task defintion. The json string should be stored in a file in the workspace. ie if you had a file called taskdef.json store in the root of the workspace alongside the drone.yml file you would set the parameter to: @@ -100,7 +100,7 @@ The json string should be stored in a file in the workspace. ie if you had a fil The intent of this setting was to be able to supply only those settings that needed to be modified and again not needing to maintain the entire definition, where some settings may have been added dynamically by the CF deployment. -Some sugar has been added to the json format in order to support some of this functionality of updating / removing / adding settings in instances where the value of the parameter in the config is an array of objects. +Some sugar has been added to the json format in order to support some of this functionality of updating / removing / adding settings in instances where the value of the parameter in the config is an array of objects. This is acomplished by a recursive merging process that drills down through the entire object processing each child individually. @@ -123,9 +123,9 @@ Output #####Modifying values that are part of an object in an array of objects -When the configurations are merged together and the plugin hits an array of objects in the provided json file, it looks to see if a 'keys' parameter exists. If it does, then it looks through all of the array items in the current config and tries to match the values of those keys in the new config to values of the keys in the old config. It's pretty much just setting up a hash of fields in order to make a uniuqe match. +When the configurations are merged together and the plugin hits an array of objects in the provided json file, it looks to see if a 'keys' parameter exists. If it does, then it looks through all of the array items in the current config and tries to match the values of those keys in the new config to values of the keys in the old config. It's pretty much just setting up a hash of fields in order to make a uniuqe match. -You can use one or more fields, just depends how specific you need to be in order to make the match. For environment vars for instance, no 2 will have the same name, so just specifying name would be enough. +You can use one or more fields, just depends how specific you need to be in order to make the match. For environment vars for instance, no 2 will have the same name, so just specifying name would be enough. If it is able to match all of the keys for a given object in the array, then it will look to merge any other parameters in the original config that exist in the new config and are not set as keys. @@ -227,14 +227,14 @@ Output #####Removing items from a simple array -This is not yet implemented. +This is not yet implemented. #####Examples -Lets say you have a full container definition in ecs like this +Lets say you have a full container definition in ecs like this ``` { containerDefinitions: - [ + [ { name: 'dashboard', image: 'registry.mydomain.com/dashboard-prod:1.8.0', cpu: 110, @@ -262,31 +262,31 @@ Lets say you have a full container definition in ecs like this And you want to; 1. modify the port mapping, 2. Add an ENV var, 3. delete an ENV var. All you would need to specify in your json file, is the structure and values for the items you want to modify. Like this: -``` +``` { containerDefinitions: - [ + [ { name: 'dashboard', portMappings: [ { containerPort: 80, hostPort: 8070, protocol: 'udp' , keys:['containerPort', 'hostPort']} ], - environment: [ + environment: [ { name: 'LOGGING_SERVER', value: 'log.mydomain.com'}, { name: 'SOMEKEY', keys: ['name'] , remove:true} ], keys: ['name','cpu'] } ] - } + } ``` Let's take the containerDefintions parameter to start with. It's in the root of the task defintiion structure and it is an array of objects detailing each container. Inside the root of the first container object, our updated file has an additional parameter called 'keys' The keys in the root of the first container in the array are 'name' & 'cpu'. So as name and cpu in the new configuration match a container in the old configuration, any parameters in the root of the container object in the new configuration not set as keys will be updated. In this case environment and portMappings can now be put through the merge process. -The best way to think about it from this point is that the merge process is now passed the values of the portMappings parameter and starts the whole process above over again, but with the following +The best way to think about it from this point is that the merge process is now passed the values of the portMappings parameter and starts the whole process above over again, but with the following ``` { portMappings: @@ -295,7 +295,7 @@ The best way to think about it from this point is that the merge process is now ] } ``` -So stepping through it again, we have portMappings at the root, which is an array of objects. It iterates through each object looking for a 'keys' parameter. In this case we have one and it's asking to match containerPort & hostPort. +So stepping through it again, we have portMappings at the root, which is an array of objects. It iterates through each object looking for a 'keys' parameter. In this case we have one and it's asking to match containerPort & hostPort. It pulls the portMappings array from the original ECS config and looks through the keys of each item to match containerPort: 80, hostPort: 8070. In our case there is a match and as there is another parameter 'protocol' in our new config which isn't in the keys array for this object, the original config parameter is updated to be protocol: 'udp' @@ -311,7 +311,7 @@ The allowed log levels are *info - outputs a guide on what's currently being done, eg Fetching clsuters from ECS. + outputs a guide on what's currently being done, eg Fetching clsuters from ECS. *fatal all ecs requests will throw an exception if there is an error @@ -328,8 +328,61 @@ The allowed log levels are ##Development -### Install +### Install ``` npm install ``` +### Example + +(these aren't real aws keys btw) + +``` +node app.js -- '{"repo":{"clone_url":"git://github.com/drone/drone","owner":"drone","name":"drone","full_name":"drone/drone"},"system":{"link_url":"https://beta.drone.io"},"build":{"number":22,"status":"success","started_at":1421029603,"finished_at":1421029813,"message":"UpdatetheReadme","author":"johnsmith","author_email":"john.smith@gmail.com","event":"push","branch":"master","commit":"436b7a6e2abaddfd35740527353e78a227ddcb2c","ref":"refs/heads/master"},"workspace":{"root":"/drone/src","path":"/drone/src/github.com/drone/drone"},"vargs":{"build":"","repo":"","access_key":"AHGDGJHhJ8677A2Q","secret_key":"IrlHJHGShjgsgsdgJHGSjhgsdgJJGU8JTRhSb","region":"ap-southeast-2","family":"Development-DashboardTaskDefinition","cluster":"Development-ServicesCluster","service":"Development-DashboardService","constainer_name":"dashboard","allow_multiple_clusters":false,"allow_multiple_services":false,"image_name":"registry.mydomain.com.au/dashboard-development","image_tag":"1.0.1","log_level":"debug","task_definition":"taskDef.json"}}' +``` + +Friendly version +``` +{"repo": {"clone_url": "git://github.com/drone/drone","owner": "drone","name": "drone", + "full_name": "drone/drone" + }, + "system": { + "link_url": "https://beta.drone.io" + }, + "build": { + "number": 22, + "status": "success", + "started_at": 1421029603, + "finished_at": 1421029813, + "message": "Update the Readme", + "author": "johnsmith", + "author_email": "john.smith@gmail.com" + "event": "push", + "branch": "master", + "commit": "436b7a6e2abaddfd35740527353e78a227ddcb2c", + "ref": "refs/heads/master" + }, + "workspace": { + "root": "/drone/src", + "path": "/drone/src/github.com/drone/drone" + }, + "vargs": { + "build": "", + "repo": "", + "access_key": "AHGDGJHhJ8677A2Q", + "secret_key": "IrlHJHGShjgsgsdgJHGSjhgsdgJJGU8JTRhSb", + "region": "ap-southeast-2", + "family": "Development-DashboardTaskDefinition", + "cluster": "Development-ServicesCluster", + "service": "Development-DashboardService", + "constainer_name": "dashboard", + "allow_multiple_clusters": false, + "allow_multiple_services": false, + "image_name": "registry.mydomain.com.au/dashboard-development", + "image_tag": "1.0.1", + "log_level": "debug", + "task_definition": "taskDef.json" + } +} +``` + diff --git a/app.js b/app.js index bffd135..351f273 100644 --- a/app.js +++ b/app.js @@ -1,15 +1,19 @@ -//const Drone = require('drone-node'); -//const plugin = new Drone.Plugin(); +'use strict' + +const Drone = require('drone-node'); +const plugin = new Drone.Plugin(); const AWS = require('aws-sdk'); const Promise = require('bluebird'); const bunyan = require('bunyan'); -const logger = bunyan.createLogger({name: 'drone-ecs-node'}); const util = require('util'); +const jsonfile = require('jsonfile'); +const humps = require('./humps'); -function ecsService (options, vargs) { +function ecsService (awsOptions, vargs, logger) { this.ecs = new AWS.ECS(awsOptions); this.vargs = vargs; + this.logger = logger; return; } @@ -26,18 +30,17 @@ ecsService.prototype = Object.create({ listClusters: function() { - logger.info('Fetching clusters from ECS'); + this.logger.info('Fetching clusters from ECS'); - var _this = this; //1. list clusters - return new Promise(function (resolve) { + return new Promise((resolve) => { - _this.ecs.listClusters({maxResults: 100}, function (err, data) { + this.ecs.listClusters({maxResults: 100}, (err, data) => { if (err) { throw new Error(err); // an error occurred } else { - logger.debug('listClusters response', util.inspect(data, { showHidden: true, depth: null })); + this.logger.debug('listClusters response', util.inspect(data, { showHidden: true, depth: null })); return resolve(data); } }); @@ -57,8 +60,8 @@ ecsService.prototype = Object.create({ var clusterArn = clusterArns[i].toLowerCase(); - if (clusterArn.indexOf(this.vargs.Cluster.toLowerCase()) != -1) { - if (this.clusterArns.length > 0 && this.vargs.AllowMultipleClusters === false) { + if (clusterArn.indexOf(this.vargs.cluster.toLowerCase()) != -1) { + if (this.clusterArns.length > 0 && this.vargs.allowMultipleClusters === false) { var error = "Matched multiple clusters, cluster name is too ambiguous!"; error += "\nSet AllowMultipleClusters to true to override"; throw new Error(error); @@ -67,21 +70,20 @@ ecsService.prototype = Object.create({ } } - if (this.clusterArns.length <= 0) { + if (Object.keys(this.clusterArns).length <= 0) { throw new Error("No matches found for cluster name!"); } }, listServices: function() { - logger.info('Fetching services from ECS'); + this.logger.info('Fetching services from ECS'); var clusterArns = Object.keys(this.clusterArns); if (clusterArns.length <= 0) { throw new Error("No matches found for cluster name!"); } var promises = []; - var _this = this; for (var x=0; x { + this.ecs.listServices(params, (err, data) => { if (err) { throw new Error(err); } data.clusterArn = clusterArn; - logger.debug('listServices callback response for cluster: ' + clusterArn, util.inspect(data, { showHidden: true, depth: null })); + this.logger.debug('listServices callback response for cluster: ' + clusterArn, util.inspect(data, { showHidden: true, depth: null })); return resolve(data); }); }); @@ -107,8 +109,8 @@ ecsService.prototype = Object.create({ //list services promises.push(promise); } - return Promise.all(promises).then(function (values) { - data = []; + return Promise.all(promises).then((values) => { + var data = []; for (var i=0; i 0 && this.vargs.AllowMultipleServices === false) { + if (service.indexOf(this.vargs.service.toLowerCase()) != -1) { + if (this.serviceArns.length > 0 && this.vargs.allowMultipleServices === false) { var error = "Matched multiple services, service name is too ambiguous!"; error += "\nSet AllowMultipleServices to true to override"; throw new Error(error); @@ -149,7 +151,7 @@ ecsService.prototype = Object.create({ } } } - + this.logger.info('Matched services: ', this.serviceArns); if (Object.keys(this.serviceArns).length <= 0) { throw new Error("No matches found for service name!"); } @@ -157,32 +159,32 @@ ecsService.prototype = Object.create({ describeServices: function() { + this.logger.info('Fetching service descriptions from ECS'); var serviceArns = Object.keys(this.serviceArns); if (serviceArns.length <= 0) { throw new Error('No serviceArns set, run listServices or setServiceArns'); } - logger.debug('describeServices this.serviceArns'); - logger.debug(this.serviceArns); + this.logger.debug('describeServices this.serviceArns'); + this.logger.debug(this.serviceArns); - var _this = this; var promises = []; - for (clusterArn in this.serviceArns) { + for (var clusterArn in this.serviceArns) { var params = { services: this.serviceArns[clusterArn], cluster: clusterArn }; - logger.debug('describeServices request parameters', params); + this.logger.debug('describeServices request parameters', params); - var promise = new Promise(function (resolve) { - _this.ecs.describeServices(params, function (err, data) { + var promise = new Promise((resolve) => { + this.ecs.describeServices(params, (err, data) => { if (err) { throw new Error(err); } - logger.debug('describeServices callback response for cluster: ' + clusterArn, util.inspect(data, { showHidden: true, depth: null })); + this.logger.debug('describeServices callback response for cluster: ' + clusterArn, util.inspect(data, { showHidden: true, depth: null })); return resolve(data); }); @@ -192,7 +194,7 @@ ecsService.prototype = Object.create({ } - return Promise.all(promises).then(function (values) { + return Promise.all(promises).then((values) => { var data = []; @@ -221,11 +223,12 @@ ecsService.prototype = Object.create({ throw new Error("No service descriptions found"); } - var _this = this; var promises = []; var services = this.serviceDescriptions; - for (serviceArn in services) { + for (var serviceArn in services) { + + this.logger.info('Fetching tasks for service: ' + serviceArn); var service = services[serviceArn]; @@ -235,13 +238,13 @@ ecsService.prototype = Object.create({ maxResults: 100, }; - var promise = new Promise(function (resolve) { - _this.ecs.listTasks(params, function (err, data) { + var promise = new Promise((resolve) => { + this.ecs.listTasks(params, (err, data) => { if (err) { throw new Error(err); // an error occurred } else { - logger.debug('listTasks callback response for service: ' + service.serviceArn, util.inspect(data, { showHidden: true, depth: null })); // successful response + this.logger.debug('listTasks callback response for service: ' + service.serviceArn, util.inspect(data, { showHidden: true, depth: null })); // successful response //inject the cluster and service //in to the response so that //we know which task belong to where @@ -259,7 +262,7 @@ ecsService.prototype = Object.create({ promises.push(promise); } - return Promise.all(promises).then(function (values) { + return Promise.all(promises).then((values) => { var tasks = []; for (var i=0; i { + this.ecs.describeTasks(params, (err, data) => { if (err) { throw new Error(err); // an error occurred } else { - logger.debug('describeTasks callback response for cluster: ' + clusterArn, util.inspect(data, { showHidden: true, depth: null })); // successful response + this.logger.debug('describeTasks callback response for cluster: ' + clusterArn, util.inspect(data, { showHidden: true, depth: null })); // successful response return resolve(data); } }); @@ -345,7 +349,7 @@ ecsService.prototype = Object.create({ } //describe all tasks - return Promise.all(promises).then(function (values) { + return Promise.all(promises).then((values) => { var data = []; for (var i=0; i { + this.ecs.describeTaskDefinition(params, (err, data) => { if (err) { throw new Error(err); // an error occurred } else { - logger.debug('describeTaskDefinitionss callback response for task def: ' + taskDefArn, util.inspect(data, { showHidden: true, depth: null })); // successful response + this.logger.debug('describeTaskDefinitionss callback response for task def: ' + taskDefArn, util.inspect(data, { showHidden: true, depth: null })); // successful response return resolve(data); } }); @@ -413,7 +424,7 @@ ecsService.prototype = Object.create({ promises.push(promise); } - return Promise.all(promises).then(function (values) { + return Promise.all(promises).then((values) => { var taskDefs = []; for (var i=0; i 0) { - logger.debug('before', util.inspect(taskDef, { showHidden: true, depth: null })); + if (this.vargs.taskDefinition !== null) { + if (Object.keys(this.vargs.taskDefinition).length > 0) { + this.logger.debug('Defintion before merge', util.inspect(taskDef, { showHidden: true, depth: null })); - taskDef = this.mergeObjects(taskDef, vargs.TaskDefinition); + taskDef = this.mergeObjects(taskDef, this.vargs.taskDefinition); - logger.debug('after', util.inspect(taskDef, { showHidden: true, depth: null })); + } } //remove fields that aren't required for new definition @@ -488,13 +499,13 @@ ecsService.prototype = Object.create({ delete taskDef.status; delete taskDef.requiresAttributes; - /* - var promise = new Promise(function (resolve) { - _this.ecs.registerTaskDefinition(taskDef, function (err, data) { + this.logger.info('Attempting to register new Task Defintion', util.inspect(taskDef, { showHidden: true, depth: null })); + var promise = new Promise((resolve) => { + this.ecs.registerTaskDefinition(taskDef, (err, data) => { if (err) { throw new Error(err); // an error occurred } else { - logger.debug('registerTaskDefinitions callback response for service: ' + serviceArn, data); // successful response + this.logger.debug('registerTaskDefinitions callback response for service: ' + serviceArn, util.inspect(data, { showHidden: true, depth: null })); // successful response //need to furnish the results with the taskArn data = { @@ -508,11 +519,10 @@ ecsService.prototype = Object.create({ } }); }); -*/ - //promises.push(promise); + promises.push(promise); } - return; - return Promise.all(promises).then(function (values) { + + return Promise.all(promises).then((values) => { var data = []; for (var i=0; i { + this.ecs.updateService(params, (err, data) => { if (err) { throw new Error(err); // an error occurred } else { - logger.debug('updateService callback response for service: ' + taskDef.serviceArn + ' task def: ' + taskDefArn, util.inspect(data, { showHidden: true, depth: null })); // successful response + this.logger.debug('updateService callback response for service: ' + taskDef.serviceArn + ' task def: ' + taskDef.family, util.inspect(data, { showHidden: true, depth: null })); // successful response return resolve(data); } }); @@ -565,10 +574,10 @@ ecsService.prototype = Object.create({ promises.push(promise); } - return Promise.all(promises).then(function (values) { + return Promise.all(promises).then((values) => { for (var i=0; i 0)) { - + oldObj[param] = this.mergeObjects(oldObj[param],newObj[param]); continue; @@ -653,7 +662,7 @@ ecsService.prototype = Object.create({ target = source; return target; } - + //its an array of objects so process the objects in the array if(typeof source[0] == 'object' && !util.isArray(source[0])) { @@ -665,22 +674,22 @@ ecsService.prototype = Object.create({ //iterate over the array and set values as needed //need to think about how to delete item from the array for (var i=0; i < source.length; i++) { - + var value = source[i]; - + if (target.indexOf(value) == -1) { target.push(value); } } - return target; - } + return target; + } if (typeof source == 'object') { for (var param in source) { if (!(param in target)) { - logger.warn(param + 'param not found in target'); + this.logger.warn(param + 'param not found in target'); continue; } @@ -701,7 +710,20 @@ ecsService.prototype = Object.create({ module.exports = ecsService; -function processBuild(params) { +plugin.parse().then(function (params) { + + params = humps.camelizeKeys(params); + + console.log(params); + // gets build and repository information for + // the current running build + const workspace = params.workspace; + const build = params.build; + const repo = params.repo; + + // gets plugin-specific parameters defined in + // the .drone.yml file + const vargs = params.vargs; var logLevels = [ @@ -715,79 +737,85 @@ function processBuild(params) { var logLevel = 'info'; - if (logLevels.indexOf(vargs.LogLevel) != -1) { - logLevel = vargs.LogLevel; + if (logLevels.indexOf(vargs.logLevel) != -1) { + logLevel = vargs.logLevel; } + + const logger = bunyan.createLogger({name: 'drone-ecs-node'}); + + logger.level(logLevel); - // gets build and repository information for - // the current running build - const build = params.build; - const repo = params.repo; - // gets plugin-specific parameters defined in - // the .drone.yml file - const vargs = params.vargs; + logger.info('Validating Paramters'); - if (vargs.AccessKey.length == 0) { + if (vargs.accessKey.length == 0) { logger.error("Please provide an access key"); return process.exit(1); } - if (vargs.SecretKey.length == 0) { + if (vargs.secretKey.length == 0) { logger.error("Please provide a secret key"); return process.exit(1); } - if (vargs.Region.length == 0) { + if (vargs.region.length == 0) { logger.error("Please provide a region"); return process.exit(1); } - if (vargs.Family.length == 0) { + if (vargs.family.length == 0) { logger.error("Please provide a task definition family name"); return process.exit(1); } - if (vargs.Cluster.length == 0) { + if (vargs.cluster.length == 0) { logger.error("Please provide a cluster name"); return process.exit(1); } - if (vargs.Image.length == 0) { + if (vargs.imageName.length == 0) { logger.error("Please provide an image name"); return process.exit(1); } - if (vargs.Service.length == 0) { + if (vargs.service.length == 0) { logger.error("Please provide a service name"); return process.exit(1); } + if (vargs.taskDefinition === '' || vargs.taskDefinition === false || vargs.taskDefinition === null) { + vargs.taskDefinition == null; - if (vargs.Tag.length == 0) { + } else { + vargs.taskDefinition = jsonfile.readFileSync(workspace.path + '/' + vargs.taskDefinition); + } + + if (vargs.imageTag.length == 0) { vargs.Tag = "latest"; } + var awsOptions = { - accessKeyId: vargs.AccessKey, - secretAccessKey: vargs.SecretKey, - region: vargs.Region, - } + accessKeyId: vargs.accessKey, + secretAccessKey: vargs.secretKey, + region: vargs.region, + }; - var ecs = new ecsService(awsOptions, vargs); + console.log(awsOptions); + + var ecs = new ecsService(awsOptions, vargs, logger); /* - if cloudformation = true 1. list clusters and find the cluster arns 2. list services in clusters and find the service arns 3. describe the services and find the task def arns @@ -798,71 +826,66 @@ function processBuild(params) { 8. if they do then updateservice */ - if (vargs.CloudFormation) { - - return ecs.listClusters() - .then(function (clusterArns) { - - ecs.processClusters(clusterArns); - return ecs.listServices(); - }) - .then(function (serviceArns) { - - ecs.processServicesList(serviceArns); - return ecs.describeServices(); - }) - .then(function (serviceDescriptions) { - - ecs.processServiceDescriptions(serviceDescriptions); - return ecs.listTasks(); - }) - .then(function (taskArns) { - - ecs.processTasksList(taskArns); - return ecs.describeTasks(); - }) - .then(function (taskDescriptions) { - - ecs.processTaskDescriptions(taskDescriptions); - return ecs.describeTaskDefinitions(); - }) - .then(function (taskDefinitions) { - - return ecs.registerTaskDefinitions(taskDefinitions); - }) - .then(function (taskDefinitions) { - - return ecs.updateServices(taskDefinitions); - }) - .then(function (services) { - - logger.info('final data', services); - logger.info('everything worked'); - return process.exit(0); - - }) - .catch(function (err) { + return ecs.listClusters() + .then(function (data) { + + logger.debug('list clusters', util.inspect(data, { showHidden: true, depth: null })); + ecs.processClusters(data); + return ecs.listServices(); + }) + .then(function (data) { + + logger.debug('list services', util.inspect(data, { showHidden: true, depth: null })); + ecs.processServicesList(data); + return ecs.describeServices(); + }) + .then(function (data) { + + logger.debug('describe services', util.inspect(data, { showHidden: true, depth: null })); + ecs.processServiceDescriptions(data); + return ecs.listTasks(); + }) + .then(function (data) { + + logger.debug('list tasks', util.inspect(data, { showHidden: true, depth: null })); + ecs.processTasksList(data); + return ecs.describeTasks(); + }) + .then(function (data) { + + logger.debug('describe tasks', util.inspect(data, { showHidden: true, depth: null })); + ecs.processTaskDescriptions(data); + return ecs.describeTaskDefinitions(); + }) + .then(function (data) { + + logger.debug('describe task definitions', util.inspect(data, { showHidden: true, depth: null })); + return ecs.registerTaskDefinitions(data); + }) + .then(function (data) { + + logger.info('Register Task Defintions Success:', util.inspect(data, { showHidden: true, depth: null })); + return ecs.updateServices(data); + }) + .then(function (data) { + + logger.info('Update Service Success:', util.inspect(data, { showHidden: true, depth: null })); + logger.info('everything worked'); + return process.exit(0); + + }) + .catch(function (err) { + + logger.fatal('ECS Error', err, err.stack); + return process.exit(1); + }); +}) +.catch(function (err) { - logger.fatal('catch', err, err.stack); - return process.exit(1); - }); - } else { - //process the normal setup. - //should still retreive the existing config - //and merge it here, but no need for searching the - //all clusters etc. Can retrieve task defs using family name - /* - what's actually happening here? - all the specified names are hard - so should be able to just do the last 2 steps - of regdefinitions and update service. - might need a way to set the services / clusters / tasks - also need to pull down the existing definition so we can merge the - new config - */ + console.log('Dron Plugin Error', err, err.stack); + return process.exit(1); +}); - } -} /* function mergeRecursive(obj1, obj2) { @@ -909,7 +932,7 @@ function mergeArrays (arr1, arr2) { } */ //testing - +/* var taskDef = { containerDefinitions: @@ -917,9 +940,9 @@ var taskDef = { name: 'dashboard', portMappings: [ { containerPort: 80, hostPort: 8070, protocol: 'udp' , keys:['containerPort', 'hostPort']}], - environment: [ + environment: [ { name: 'TEST', value: '1234'}, - { name: 'NODE_ENV', value: 'test', keys: ['name'] , remove:true} + { name: 'NODE_ENV', value: 'test', keys: ['name']} ], links: ['a'], keys: ['name'] @@ -967,7 +990,7 @@ var a = { environment: }; var b = { - environment: [ + environment: [ { name: 'TEST', value: '1234'}, { name: 'NODE_ENV', value: 'test', keys: ['name'] } ] @@ -993,6 +1016,14 @@ var vargs = { TaskDefinition: taskDef } +//console.log('args', process.argv); + +params = JSON.parse(process.argv[3]); + +vargs = params.vargs; +console.log(vargs.TaskDefinition); +vargs.TaskDefinition = null;//jsonfile.readFileSync('/data/dev/golang/src/github.com/fridaystreet/drone-ecs-node/'+vargs.TaskDefinition); + var logLevels = [ 'info', 'fatal', @@ -1056,24 +1087,19 @@ var ecs = new ecsService(awsOptions, vargs); }) .then(function (data) { - logger.debug('final', util.inspect(data, { showHidden: true, depth: null })); - }) -/* - .then(function (data) { - + logger.info('Register Task Defintions Success:', util.inspect(data, { showHidden: true, depth: null })); return ecs.updateServices(data); }) .then(function (data) { - logger.info('final data', data); + logger.info('Update Service Success:', util.inspect(data, { showHidden: true, depth: null })); logger.info('everything worked'); return process.exit(0); }) -*/ .catch(function (err) { logger.error('catch', err, err.stack); return process.exit(1); }); - +*/ diff --git a/humps b/humps new file mode 160000 index 0000000..c0e417d --- /dev/null +++ b/humps @@ -0,0 +1 @@ +Subproject commit c0e417dea4102880c93dd955121b8a58a1235635 diff --git a/package.json b/package.json index 5d6b640..53517a9 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "aws-sdk": "^2.2.33", "bluebird": "^3.2.2", "bunyan": "^1.5.1", - "q": "^1.4.1" + "drone-node": "^1.0.1", + "jsonfile": "^2.2.3", }, "devDependencies": {}, "scripts": { diff --git a/qtest.js b/qtest.js deleted file mode 100644 index 368b778..0000000 --- a/qtest.js +++ /dev/null @@ -1,62 +0,0 @@ -var Q = require("q"); - -var oneA = function () { - var d = Q.defer(); - var timeUntilResolve = Math.floor((Math.random()*2000)+1); - console.log('1A Starting'); - setTimeout(function () { - console.log('1A Finished'); - d.resolve('1ATime: ' + timeUntilResolve); - }, timeUntilResolve); - return d.promise; -}; - -var oneB = function () { - var d = Q.defer(); - var timeUntilResolve = Math.floor((Math.random()*2000)+1); - console.log('1B Starting'); - setTimeout(function () { - console.log('1B Finished'); - d.resolve('1BTime: ' + timeUntilResolve); - }, timeUntilResolve); - return d.promise; -}; - -// This fuction throws an error which later on we show will be handled -var two = function (oneATime, oneBTime) { - var d = Q.defer(); - console.log('OneA: ' + oneATime + ', OneB: ' + oneBTime); - console.log('2 Starting and Finishing, so 3A and 3B should start'); - d.resolve(); - //return d.promise; -}; - -var threeA = function () { - var d = Q.defer(); - console.log('3A Starting'); - setTimeout(function () { - console.log('3A Finished'); - d.resolve(); - }, Math.floor((Math.random()*2000)+1)); - return d.promise; -}; - -var threeB = function () { - var d = Q.defer(); - console.log('3B Starting'); - setTimeout(function () { - console.log('3B Finished'); - d.resolve(); - }, Math.floor((Math.random()*5000)+1)); - return d.promise; -}; - -var four = function () { - console.log('Four is now done'); -}; - -Q.all([ oneA(), oneB() ]) -.spread(two) -.then(function () { return Q.all([ threeA(), threeB() ]); }) -.then(four) -.done(); \ No newline at end of file diff --git a/taskDef.json b/taskDef.json new file mode 100644 index 0000000..108de29 --- /dev/null +++ b/taskDef.json @@ -0,0 +1,15 @@ +{ + "containerDefinitions": + [ { + "name": "dashboard", + "portMappings": + [ { "containerPort": 80, "hostPort": 8070, "protocol": "udp" , "keys":["containerPort", "hostPort"]}], + "environment": [ + { "name": "TEST", "value": "1234"}, + { "name": "NODE_ENV", "value": "test", "keys": ["name"]} + ], + "links": ["a"], + "keys": ["name"] + } + ] +} \ No newline at end of file