Skip to content

Commit 7a853b0

Browse files
author
Steven Nemetz
committed
Support budget by tag
1 parent ed3a504 commit 7a853b0

File tree

12 files changed

+177
-18
lines changed

12 files changed

+177
-18
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
Terraform module to setup AWS budgets and notifications
44

5-
This is fairly specific for now. It can create budgets for multiple linked accounts. Each with it's own limit.
5+
For now it can manage 2 types of budgets:
6+
- Account budgets for a single account or multiple linked accounts. Each with it's own limit.
7+
- Budget by tag
68
Notification is only supported for a single email address
79

810
NOTE: Setting up notification and subscribers is not currently supported in Terraform

examples/cost-tag-monthly/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Example: Monthly budget by tag

examples/cost-tag-monthly/main.tf

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module "budget-1" {
2+
source = "../../"
3+
4+
budget_name = "service-1"
5+
budget_name_prefix = "Testing-"
6+
tag_value = "service-1"
7+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
provider "aws" {
2+
region = "${var.region}"
3+
4+
# Make it faster by skipping something
5+
skip_get_ec2_platforms = true
6+
skip_metadata_api_check = true
7+
skip_region_validation = true
8+
skip_credentials_validation = true
9+
skip_requesting_account_id = true
10+
}
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
variable "region" {
2+
default = "us-west-2"
3+
}

examples/cost-tag-quarterly/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Example: Quarterly budget by tag

examples/cost-tag-quarterly/main.tf

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module "budget-1" {
2+
source = "../../"
3+
4+
budget_name = "service-1"
5+
budget_name_prefix = "Testing-"
6+
tag_value = "service-1"
7+
time_unit = "QUARTERLY"
8+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
provider "aws" {
2+
region = "${var.region}"
3+
4+
# Make it faster by skipping something
5+
skip_get_ec2_platforms = true
6+
skip_metadata_api_check = true
7+
skip_region_validation = true
8+
skip_credentials_validation = true
9+
skip_requesting_account_id = true
10+
}
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
variable "region" {
2+
default = "us-west-2"
3+
}

main.tf

+39-3
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,70 @@ data "aws_caller_identity" "current" {}
55

66
# https://docs.aws.amazon.com/aws-cost-management/latest/APIReference/API_budgets_CostTypes.html
77
#
8-
/*
8+
/**/
99
# This is future work for single budget that can set most things
1010
locals {
1111
cost_filters = {
1212
AZ = {
1313
AZ = ""
1414
}
15+
1516
LinkedAccount = {
16-
LinkedAccount = "${join(",", var.account_ids)}"
17+
#LinkedAccount = "${join(",", var.account_ids)}"
1718
}
19+
1820
Operation = {
1921
Operation = ""
2022
}
23+
2124
PurchaseType = {
2225
PurchaseType = ""
2326
}
27+
2428
Service = {
2529
Service = ""
2630
}
31+
2732
TagKeyValue = {
28-
TagKeyValue = "Stack$$${var.service}"
33+
TagKeyValue = "user:${var.tag_key}$$${var.tag_value}"
2934
}
35+
3036
UsageType = {
3137
UsageType = ""
3238
}
3339
}
3440
}
41+
3542
/**/
43+
resource "aws_budgets_budget" "budget" {
44+
count = "${length(var.budgets) == 0 ? 1 : 0}"
45+
account_id = "${data.aws_caller_identity.current.account_id}"
46+
name = "${var.budget_name_prefix}${var.budget_name}-${title(lower(var.time_unit))}"
47+
budget_type = "${var.budget_type}"
48+
limit_unit = "${var.limit_unit}"
49+
time_unit = "${var.time_unit}"
50+
cost_filters = "${local.cost_filters[var.cost_filter_type]}"
51+
52+
limit_amount = "${var.time_unit == "ANNUALLY" ?
53+
var.limit_amount * 12 :
54+
var.time_unit == "QUARTERLY" ?
55+
var.limit_amount * 3 :
56+
var.limit_amount
57+
}"
58+
59+
time_period_start = "${var.time_unit == "ANNUALLY" ?
60+
"${substr(timestamp(), 0, 5)}01-01_00:00" :
61+
var.time_unit == "QUARTERLY" ?
62+
"${substr(timestamp(), 0, 5)}${format("%02d", (substr(timestamp(), 5, 2) - 1) / 3 * 3 + 1)}-01_00:00" :
63+
"${substr(timestamp(), 0, 8)}01_00:00"
64+
}"
65+
66+
lifecycle {
67+
ignore_changes = ["time_period_start"]
68+
}
69+
}
70+
71+
# Multiple linked account budgets
3672
resource "aws_budgets_budget" "budgets" {
3773
count = "${length(var.budgets)}"
3874
account_id = "${data.aws_caller_identity.current.account_id}"

notifications.tf

+66-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@
77
# https://docs.aws.amazon.com/sns/latest/api/API_Subscribe.html#API_Subscribe_RequestParameters
88

99
locals {
10+
# Prefix for single budget
11+
notification_cmd_prefix_single = <<EOF
12+
aws budgets create-notification \
13+
--account-id ${data.aws_caller_identity.current.account_id} \
14+
--budget-name ${var.budget_name_prefix}${var.budget_name}-${title(lower(var.time_unit))} \
15+
--subscribers ${join(" ", formatlist("SubscriptionType=EMAIL,Address=%s", var.emails))} \
16+
--notification \
17+
EOF
18+
1019
# Prefix for multiple budgets. So only contain stuff that is common to all budgets in var.budgets
1120
# Could move subscribers here as long as only supporting one
1221
notification_cmd_prefix = <<EOF
@@ -15,8 +24,62 @@ aws budgets create-notification \
1524
EOF
1625
}
1726

27+
//
28+
// Notifications for single budget
29+
//
30+
# When forecasted bill exceeds budget by 10%
31+
resource "null_resource" "budget_forecast_110" {
32+
count = "${length(var.budgets) == 0 ? 1 : 0}"
33+
34+
triggers {
35+
budget_id = "${aws_budgets_budget.budget.id}"
36+
prefix = "${local.notification_cmd_prefix_single}"
37+
}
38+
39+
provisioner "local-exec" {
40+
command = <<CMD
41+
${local.notification_cmd_prefix_single} NotificationType=FORECASTED,ComparisonOperator=GREATER_THAN,Threshold=110,ThresholdType=PERCENTAGE
42+
CMD
43+
}
44+
}
45+
46+
# When actual bill exceeds 90% budget
47+
resource "null_resource" "budget_actual_90" {
48+
count = "${length(var.budgets) == 0 ? 1 : 0}"
49+
50+
triggers {
51+
budget_id = "${aws_budgets_budget.budget.id}"
52+
prefix = "${local.notification_cmd_prefix_single}"
53+
}
54+
55+
provisioner "local-exec" {
56+
command = <<CMD
57+
${local.notification_cmd_prefix_single} NotificationType=ACTUAL,ComparisonOperator=GREATER_THAN,Threshold=90,ThresholdType=PERCENTAGE
58+
CMD
59+
}
60+
}
61+
62+
# When actual bill exceeds 80% budget
63+
resource "null_resource" "budget_actual_80" {
64+
count = "${length(var.budgets) == 0 ? 1 : 0}"
65+
66+
triggers {
67+
budget_id = "${aws_budgets_budget.budget.id}"
68+
prefix = "${local.notification_cmd_prefix_single}"
69+
}
70+
71+
provisioner "local-exec" {
72+
command = <<CMD
73+
${local.notification_cmd_prefix_single} NotificationType=ACTUAL,ComparisonOperator=GREATER_THAN,Threshold=80,ThresholdType=PERCENTAGE
74+
CMD
75+
}
76+
}
77+
78+
//
79+
// Notifications for multiple budgets
80+
//
1881
# When forecasted bill exceeds budget by 10%
19-
resource "null_resource" "budget_forecast_notification" {
82+
resource "null_resource" "budgets_forecast_110" {
2083
count = "${length(var.budgets)}"
2184

2285
triggers {
@@ -34,7 +97,7 @@ CMD
3497
}
3598

3699
# When actual bill exceeds 90% budget
37-
resource "null_resource" "budget_actual_90" {
100+
resource "null_resource" "budgets_actual_90" {
38101
count = "${length(var.budgets)}"
39102

40103
triggers {
@@ -52,7 +115,7 @@ CMD
52115
}
53116

54117
# When actual bill exceeds 80% budget
55-
resource "null_resource" "budget_actual_80" {
118+
resource "null_resource" "budgets_actual_80" {
56119
count = "${length(var.budgets)}"
57120

58121
triggers {

variables.tf

+26-11
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
variable "service" {
2-
description = "Not used yet"
3-
default = "cmp-service"
4-
}
5-
61
// Specific to module
72
variable "budget_name_prefix" {
83
description = "Text to prefix budget names with"
94
default = ""
105
}
116

7+
variable "budget_name" {
8+
description = "Budget name (when managing a single budget)"
9+
default = ""
10+
}
11+
1212
variable "budget_type" {
1313
description = "Type of budget"
1414
default = "COST"
@@ -20,6 +20,11 @@ variable "budgets" {
2020
default = []
2121
}
2222

23+
variable "cost_filter_type" {
24+
description = "Cost filter type (when managing a single budget)"
25+
default = "TagKeyValue"
26+
}
27+
2328
variable "limit_amount" {
2429
description = "Budget limit amount"
2530
default = "100"
@@ -30,17 +35,27 @@ variable "limit_unit" {
3035
default = "USD"
3136
}
3237

38+
variable "tag_key" {
39+
description = "Tag key for cost filter by tag (TagKeyValue)"
40+
default = "Stack"
41+
}
42+
43+
variable "tag_value" {
44+
description = "Tag value for cost filter by tag (TagKeyValue)"
45+
default = ""
46+
}
47+
3348
variable "time_unit" {
3449
description = "Budget time period"
3550
default = "MONTHLY"
3651
}
3752

38-
variable "cost_filter_type" {
39-
description = "Not used yet"
40-
default = "TagKeyValue"
41-
}
42-
4353
variable "email" {
44-
description = "Email address to send budget alerts to"
54+
description = "Email address to send budget alerts to. Used by account budgets."
4555
default = ""
4656
}
57+
58+
variable "emails" {
59+
description = "List of email addresses to send budget alerts to. Maximum of 10. Used by single budgets"
60+
default = []
61+
}

0 commit comments

Comments
 (0)