├── .gitignore ├── README.md ├── index.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serverless-plugin-resource-tagging 2 | 3 | Serverless stackTags will update the tags for all the resources that support tagging. But the issue is it will update once during create. If you update the tag values after deployment, it wont reflect in next deployment. 4 | We have to remove the stack and redeploy to get the new tags reflect. This plugin will solve that issue for AWS. 5 | 6 | #### Note: 7 | - This plugin is only for AWS. 8 | - This plugin will support APIGateway stage tags even if stage is not configured in serverless.yml and clouformation created one. 9 | 10 | ### Using this pluging 11 | ``` 12 | npm install serverless-plugin-resource-tagging 13 | ``` 14 | 15 | ### serverless.yml 16 | ``` 17 | provider: 18 | name: XXX 19 | stackTags: 20 | Tag1: "Tag1 value" 21 | Tag2: "Tag2 value" 22 | plugins: 23 | - serverless-plugin-resource-tagging 24 | ``` 25 | 26 | ### Suported AWS resources 27 | ``` 28 | AWS::Lambda::Function 29 | AWS::SQS::Queue 30 | AWS::Kinesis::Stream 31 | AWS::DynamoDB::Table 32 | AWS::S3::Bucket 33 | AWS::ApiGateway::Stage 34 | AWS::CloudFront::Distribution 35 | AWS::Logs::LogGroup 36 | ``` 37 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('underscore'); 4 | 5 | class ServerlessPlugin { 6 | constructor(serverless, options) { 7 | this.serverless = serverless; 8 | this.options = options || {}; 9 | this.provider = serverless ? serverless.getProvider('aws') : null; 10 | this.service = serverless.service; 11 | this.stage = null; 12 | this.region = null; 13 | this.isApiGatewayStageAvailableInTemplate = false; 14 | this.supportedTypes = [ 15 | "AWS::Lambda::Function", 16 | "AWS::SQS::Queue", 17 | "AWS::Kinesis::Stream", 18 | "AWS::DynamoDB::Table", 19 | "AWS::S3::Bucket", 20 | "AWS::ApiGateway::Stage", 21 | "AWS::CloudFront::Distribution", 22 | "AWS::Logs::LogGroup" 23 | ]; 24 | 25 | if (!this.provider) { 26 | throw new Error('This plugin must be used with AWS'); 27 | } 28 | 29 | this.hooks = { 30 | 'deploy:finalize': this._addAPIGatewayStageTags.bind(this), 31 | 'after:deploy:deploy': this._addTagsToResource.bind(this), 32 | 'after:aws:package:finalize:mergeCustomProviderResources': this._addTagsToResource.bind(this) 33 | }; 34 | } 35 | 36 | _addTagsToResource() { 37 | var stackTags = []; 38 | var self = this; 39 | const template = this.serverless.service.provider.compiledCloudFormationTemplate; 40 | 41 | 42 | this.stage = this.serverless.service.provider.stage; 43 | if (this.options.stage) { 44 | this.stage = this.options.stage; 45 | } 46 | 47 | this.region = this.serverless.service.provider.region; 48 | if (this.options.region) { 49 | this.region = this.options.region; 50 | } 51 | 52 | if (typeof this.serverless.service.provider.stackTags === 'object') { 53 | var tags = this.serverless.service.provider.stackTags 54 | Object.keys(tags).forEach(function (key) { 55 | stackTags.push({ "Key": key, "Value": tags[key] }) 56 | }); 57 | } 58 | 59 | Object.keys(template.Resources).forEach(function (key) { 60 | var resourceType = template.Resources[key]['Type'] 61 | if ((self.supportedTypes.indexOf(resourceType) !== -1) && Array.isArray(stackTags) && stackTags.length > 0) { 62 | if (template.Resources[key]['Properties']) { 63 | var tags = template.Resources[key]['Properties']['Tags'] 64 | if (tags) { 65 | template.Resources[key]['Properties']['Tags'] = tags.concat(stackTags.filter(obj => (self._getTagNames(tags).indexOf(obj["Key"]) === -1))) 66 | } else { 67 | template.Resources[key]['Properties']['Tags'] = stackTags 68 | } 69 | } else { 70 | self.serverless.cli.log('Properties not available for ' + resourceType); 71 | } 72 | } 73 | 74 | //Flag to avoid _addAPIGatewayStageTags() call if stage config is available in serverless.yml 75 | if (resourceType === "AWS::ApiGateway::Stage") { 76 | self.isApiGatewayStageAvailableInTemplate = true; 77 | } 78 | }); 79 | self.serverless.cli.log('Updated AWS resource tags..'); 80 | } 81 | 82 | _addAPIGatewayStageTags() { 83 | var self = this; 84 | var stackName = this.provider.naming.getStackName(); 85 | if (!self.isApiGatewayStageAvailableInTemplate) { 86 | return this.provider.request('CloudFormation', 'describeStackResources', { StackName: stackName }) 87 | .then(function (resp) { 88 | var promiseStack = []; 89 | _.each(_.filter(resp.StackResources, resource => resource.ResourceType === 'AWS::ApiGateway::RestApi'), function (resource) { 90 | var apiStageParams = { 91 | resourceArn: 'arn:aws:apigateway:' + self.region + '::/restapis/' + resource.PhysicalResourceId + '/stages/' + self.stage, 92 | tags: self.service.provider.stackTags 93 | }; 94 | promiseStack.push(self.provider.request('APIGateway', 'tagResource', apiStageParams)) 95 | }); 96 | return Promise.all(promiseStack).then(resp => self.serverless.cli.log('Updated APIGateway resource tags..')); 97 | }); 98 | } else { 99 | self.serverless.cli.log('APIGateway stage already available in serverless.yml. Tag update skipped.'); 100 | return null; 101 | } 102 | } 103 | 104 | _getTagNames(srcArray) { 105 | var tagNames = [] 106 | srcArray.forEach(function (element) { 107 | tagNames.push(element["Key"]) 108 | }); 109 | return tagNames 110 | } 111 | } 112 | 113 | module.exports = ServerlessPlugin; 114 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-plugin-resource-tagging", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "user": { 8 | "version": "0.0.0", 9 | "resolved": "https://registry.npmjs.org/user/-/user-0.0.0.tgz", 10 | "integrity": "sha1-8n8bI/xRHyqO+kDbVc+6Ejgk4Co=" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-plugin-resource-tagging", 3 | "version": "1.2.0", 4 | "description": "Add tags to AWS resources", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ilayanambi86/serverless-plugin-resource-tagging.git" 12 | }, 13 | "keywords": [ 14 | "serverless plugin api gateway stage tagging", 15 | "serverless plugin", 16 | "api gateway stage tagging", 17 | "aws serverless api gateway plugin", 18 | "api gateway stack tagging", 19 | "stackTags", 20 | "api gateway" 21 | ], 22 | "author": "Ilayanambi", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/ilayanambi86/serverless-plugin-resource-tagging/issues" 26 | }, 27 | "homepage": "https://github.com/ilayanambi86/serverless-plugin-resource-tagging#readme", 28 | "dependencies": { 29 | "user": "0.0.0", 30 | "underscore": "^1.12.1" 31 | } 32 | } 33 | --------------------------------------------------------------------------------