Created
December 5, 2018 08:37
-
-
Save junkangli/fff222fba6110214e08d1649817b822a to your computer and use it in GitHub Desktop.
This is an AWS Lambda function that gets the various AWS costs for the previous month and publishes the result to a SNS topic. It is meant to be triggered on a monthly schedule by a CloudWatch event rule.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const AWS = require('aws-sdk'); | |
const snsTopicArn = process.env.SNSTopicArn; | |
const idepAccountNumbers = process.env.IDEPAWSAccountNumbers.split(',').map(item => item.trim()); | |
const ninetyninesmeAccountNumbers = process.env.Project99SMEAWSAccountNumbers.split(',').map(item => item.trim()); | |
AWS.config.update({ region: 'us-east-1' }); | |
const costexplorer = new AWS.CostExplorer(); | |
const getTotal = (params) => { | |
return costexplorer.getCostAndUsage(params).promise(); | |
}; | |
const getIDEPTotal = (params) => { | |
params.Filter = { | |
And: [{ | |
Dimensions: { | |
Key: 'LINKED_ACCOUNT', | |
Values: idepAccountNumbers | |
} | |
}, { | |
Dimensions: { | |
Key: 'RECORD_TYPE', | |
Values: ['Usage'] | |
} | |
}] | |
}; | |
return costexplorer.getCostAndUsage(params).promise(); | |
}; | |
const get99SMETotal = (params) => { | |
params.Filter = { | |
And: [{ | |
Dimensions: { | |
Key: 'LINKED_ACCOUNT', | |
Values: ninetyninesmeAccountNumbers | |
} | |
}, { | |
Dimensions: { | |
Key: 'RECORD_TYPE', | |
Values: ['Usage'] | |
} | |
}] | |
}; | |
return costexplorer.getCostAndUsage(params).promise(); | |
}; | |
const getMarketplaceCost = (params) => { | |
params.Filter = { | |
Dimensions: { | |
Key: 'BILLING_ENTITY', | |
Values: ['AWS Marketplace'] | |
} | |
}; | |
return costexplorer.getCostAndUsage(params).promise(); | |
}; | |
const getServiceCost = (params) => { | |
params.Filter = { | |
Not: { | |
Dimensions: { | |
Key: 'BILLING_ENTITY', | |
Values: ['AWS Marketplace'] | |
} | |
} | |
}; | |
return costexplorer.getCostAndUsage(params).promise(); | |
}; | |
const getRefund = (params) => { | |
params.Filter = { | |
Dimensions: { | |
Key: 'RECORD_TYPE', | |
Values: ['Refund'] | |
} | |
}; | |
return costexplorer.getCostAndUsage(params).promise(); | |
}; | |
const getSupportCost = (refund) => { | |
refund.amount = -1 * refund.amount; | |
return refund; | |
}; | |
const parseResponse = (data) => { | |
console.log(JSON.stringify(data)); | |
let amount = parseFloat(data["ResultsByTime"][0]["Total"]["UnblendedCost"]["Amount"]); | |
let unit = data["ResultsByTime"][0]["Total"]["UnblendedCost"]["Unit"]; | |
let estimated = data["ResultsByTime"][0]["Estimated"]; | |
return { | |
unit: unit, | |
amount: amount.toFixed(2), | |
estimated: estimated | |
}; | |
}; | |
const formatItemCost = (label, cost) => { | |
return label + ': ' + cost.unit + ' ' + cost.amount + (cost.estimated ? '*' : '') + '\n'; | |
}; | |
exports.handler = (event, context, callback) => { | |
const now = new Date(); | |
let startDate = new Date(now.getFullYear(), now.getMonth() - 1, 1); | |
let endDate = new Date(now.getFullYear(), now.getMonth(), 1); | |
let start = startDate.toISOString().slice(0, 10); | |
let end = endDate.toISOString().slice(0, 10); | |
var params = { | |
Granularity: "MONTHLY", | |
Metrics: ["UnblendedCost"], | |
TimePeriod: { | |
Start: start, | |
End: end | |
} | |
}; | |
let getMarketplaceCostPromise = getMarketplaceCost(params); | |
let getServiceCostPromise = getServiceCost(params); | |
let getRefundPromise = getRefund(params); | |
let getIDEPTotalPromise = getIDEPTotal(params); | |
let get99SMETotalPromise = get99SMETotal(params); | |
let eventText = 'Month of ' + startDate.toLocaleString('en-us', {month: "long"}) + '\n'; | |
Promise.all([getMarketplaceCostPromise, getServiceCostPromise, getRefundPromise, getIDEPTotalPromise, get99SMETotalPromise]) | |
.then(function (data) { | |
let marketplaceCost = parseResponse(data[0]); | |
let serviceCost = parseResponse(data[1]); | |
let refund = parseResponse(data[2]); | |
let idepCost = parseResponse(data[3]); | |
let proj99smeCost = parseResponse(data[4]); | |
eventText += formatItemCost('AWS Marketplace', marketplaceCost); | |
eventText += formatItemCost('AWS Services', serviceCost); | |
eventText += formatItemCost('AWS Support (derived from refunds)', getSupportCost(refund)); | |
eventText += formatItemCost('IDEP Sub-total', idepCost); | |
eventText += formatItemCost('99%SME Sub-total', proj99smeCost); | |
if (marketplaceCost.estimated || serviceCost.estimated || refund.estimated || idepCost.estimated || proj99smeCost.estimated) { | |
eventText += '*Values are estimated.'; | |
} | |
console.log(eventText); | |
AWS.config.update({ region: 'ap-southeast-1' }); | |
var snsParams = { | |
Message: eventText, | |
Subject: "Monthly AWS Costs", | |
TopicArn: snsTopicArn | |
}; | |
var publishPromise = new AWS.SNS().publish(snsParams).promise(); | |
publishPromise.then( | |
function(snsResponse) { | |
console.log(`Message sent to topic ${snsParams.TopicArn}`); | |
console.log("MessageID is " + snsResponse.MessageId); | |
}).catch( | |
function(err) { | |
console.error(err, err.stack); | |
} | |
); | |
callback(null, data); | |
}); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment