├── .gitignore ├── History.md ├── Makefile ├── Readme.md ├── index.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.0.0 - January 7, 2015 3 | ------------------------- 4 | - change the billing API per product 5 | 6 | 0.0.1 - February 28, 2014 7 | ------------------------- 8 | :sparkles: 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | node_modules: package.json 3 | @npm install 4 | 5 | test: node_modules 6 | @./node_modules/.bin/mocha --reporter spec 7 | 8 | .PHONY: test -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # aws-billing 3 | 4 | A node API to learn how much your Amazon Web Services hosting costs. 5 | 6 | ## Installation 7 | 8 | $ npm install aws-billing 9 | 10 | ## Example 11 | 12 | First, set up [programmatic billing access](http://docs.aws.amazon.com/awsaccountbilling/latest/about/programaccess.html). 13 | 14 | ```js 15 | var accountId = '1111-2222-3333'; 16 | var key = 'DJ9289DSAKSI2938A'; 17 | var secret = 'b/4239+skdj292jd92jd29dj229'; 18 | var bucket = 'company-aws-billing'; 19 | var region = 'us-west-2'; 20 | ``` 21 | 22 | Then you can query your billing cost: 23 | 24 | ```js 25 | var billing = require('aws-billing')(accountId, key, secret, bucket, region); 26 | 27 | billing(function (err, costs) { 28 | // .. 29 | }); 30 | ``` 31 | 32 | The `costs` variable shows the costs for the current billing period by product. 33 | 34 | ```js 35 | { total: 4839.25, 36 | start: Thu Jan 01 2015 00:00:00 GMT+0000 (UTC), 37 | end: Thu Jan 08 2015 02:34:50 GMT+0000 (UTC), 38 | products: 39 | { 'data transfer': 432.12, 40 | 'elastic mapreduce': 864.43, 41 | 'cloudfront': 124.42, 42 | 'support (business)': 120.12, 43 | 'elasticache': 124.12, 44 | 'simple storage service': 172.46, 45 | 'redshift': 423.77, 46 | 'elastic compute cloud': 123.32, 47 | 'route 53': 454.73 } } 48 | ``` 49 | 50 | ## License 51 | 52 | MIT -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | var bind = require('bind'); 3 | var csv = require('csv'); 4 | var debug = require('debug')('aws-billing'); 5 | var Ec2 = require('awssum-amazon-ec2').Ec2; 6 | var knox = require('knox'); 7 | var Dates = require('date-math'); 8 | 9 | /** 10 | * Expose `AWSBilling`. 11 | */ 12 | 13 | module.exports = AWSBilling; 14 | 15 | /** 16 | * Create a new `AWSBilling` instance given the AWS `key`, `secret`, 17 | * and S3 `bucket` and `region`. 18 | * 19 | * @param {String} accountId 20 | * @param {String} key 21 | * @param {String} secret 22 | * @param {String} bucket 23 | * @param {String} region 24 | */ 25 | 26 | function AWSBilling (accountId, key, secret, bucket, region) { 27 | if (!(this instanceof AWSBilling)) return new AWSBilling(accountId, key, secret, bucket, region); 28 | if (!accountId) throw new Error('AWS Billing requires a accountId.'); 29 | if (!key) throw new Error('AWS Billing requires a key.'); 30 | if (!secret) throw new Error('AWS Billing requires a secret.'); 31 | if (!bucket) throw new Error('AWS Billing requires a bucket.'); 32 | if (!region) throw new Error('AWS Billing requires a region.'); 33 | this.accountId = accountId; 34 | this.knox = knox.createClient({ key: key, secret: secret, bucket: bucket }); 35 | this.ec2 = new Ec2({ accessKeyId: key, secretAccessKey: secret, region: region }); 36 | var self = this; 37 | bind.all(this); 38 | return function () { return self.get.apply(self, arguments); }; 39 | } 40 | 41 | /** 42 | * Get the billing information. 43 | * 44 | * @param {Function} callback 45 | */ 46 | 47 | AWSBilling.prototype.get = function (callback) { 48 | this.products(function (err, products) { 49 | if (err) return callback(err); 50 | var total = 0.0; 51 | Object.keys(products).forEach(function (product) { 52 | total += products[product]; 53 | }); 54 | callback(null, { 55 | total: total, 56 | start: Dates.month.floor(new Date()), 57 | end: new Date(), 58 | products: products 59 | }); 60 | }); 61 | }; 62 | 63 | /** 64 | * Get the cost of AWS products 65 | * 66 | * @param {Function} callback 67 | */ 68 | 69 | AWSBilling.prototype.products = function (callback) { 70 | var accountId = this.accountId.replace(/-/g, ''); 71 | var now = new Date(); 72 | var file = accountId + '-aws-billing-csv-' + 73 | now.getFullYear() + '-' + pad(now.getMonth() + 1, 2) + '.csv'; 74 | debug('getting S3 file %s ..', file); 75 | this.knox.getFile(file, function (err, stream) { 76 | if (err) return callback(err); 77 | debug('got S3 stream ..'); 78 | csv() 79 | .from.stream(stream) 80 | .to.array(function (data) { 81 | var products = {}; 82 | var productCol = data[0].indexOf('ProductCode') + 1; 83 | var costCol = data[0].indexOf('TotalCost'); 84 | data.forEach(function (row) { 85 | var product = row[productCol].toLowerCase() 86 | .replace(/amazon /, '') 87 | .replace(/aws /, ''); 88 | var cost = parseFloat(row[costCol]); 89 | if (product && cost > 0) { 90 | if (!products[product]) products[product] = 0; 91 | products[product] += cost; 92 | } 93 | }); 94 | debug('parsed AWS product costs'); 95 | callback(err, products); 96 | }); 97 | }); 98 | }; 99 | 100 | /** 101 | * Pad a number with 0s. 102 | * 103 | * Credit: http://stackoverflow.com/questions/10073699/pad-a-number-with-leading-zeros-in-javascript 104 | * 105 | * @param {Number} n 106 | * @param {Number} width 107 | * @param {Number} z 108 | * @return {String} 109 | */ 110 | 111 | function pad(n, width, z) { 112 | z = z || '0'; 113 | n = n + ''; 114 | return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; 115 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-billing", 3 | "version": "1.0.0", 4 | "repository": "git://github.com/segmentio/aws-billing.git", 5 | "license": "MIT", 6 | "description": "AWS billing API for node", 7 | "keywords": [ 8 | "AWS", 9 | "amazon", 10 | "billing", 11 | "node", 12 | "API" 13 | ], 14 | "dependencies": { 15 | "awssum": "~1.2.0", 16 | "awssum-amazon": "~1.3.0", 17 | "awssum-amazon-ec2": "~1.4.0", 18 | "bind": "git://github.com/ianstormtaylor/bind", 19 | "csv": "~0.3.7", 20 | "date-math": "0.0.1", 21 | "debug": "~0.7.4", 22 | "knox": "~0.8.9" 23 | }, 24 | "devDependencies": { 25 | "mocha": "~1.17.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert'); 3 | var Billing = require('..'); 4 | 5 | describe('aws-billing', function () { 6 | 7 | var accountId = '1111-2222-3333'; 8 | var key = 'DJ9289DSAKSI2938A'; 9 | var secret = 'b/4239+skdj292jd92jd29dj229'; 10 | var bucket = 'company-aws-billing'; 11 | var region = 'us-west-2'; 12 | 13 | describe('#get', function () { 14 | this.timeout(30000); // aws takes a while 15 | it('should get the aws bill', function (done) { 16 | var billing = Billing(accountId, key, secret, bucket, region); 17 | billing(function (err, costs) { 18 | if (err) return done(err); 19 | assert(costs); 20 | assert(costs.total); 21 | done(); 22 | }); 23 | }); 24 | }); 25 | }); --------------------------------------------------------------------------------