Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cost-control): create budgets module #28

Merged
merged 2 commits into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions examples/aws/cost-control/budgets/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module "budgets" {
source = "../../../../modules/aws/cost-control/budgets"

budgets = {
RDSMonthly = {
limit_amount = 100
cost_filter = [{
name = "Service"
values = ["Amazon Relational Database Service"]
}]
}
EC2Monthly = {
limit_amount = 150
cost_filter = [{
name = "Service"
values = ["Amazon Elastic Compute Cloud - Compute"]
}]
}
}

notifications = [
{
comparison_operator = "GREATER_THAN"
notification_type = "ACTUAL"
threshold = 100
threshold_type = "PERCENTAGE"
subscriber_email_addresses = []
}
]
}
136 changes: 136 additions & 0 deletions modules/aws/cost-control/budgets/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
data "aws_caller_identity" "current" {}

resource "aws_budgets_budget" "this" {
for_each = var.budgets

name = each.key
budget_type = each.value.budget_type
limit_amount = each.value.limit_amount
limit_unit = lookup(each.value, "limit_unit", "USD")
time_period_start = lookup(each.value, "time_period_start", null)
time_period_end = lookup(each.value, "time_period_end", null)
time_unit = lookup(each.value, "time_unit", "MONTHLY")

dynamic "cost_types" {
for_each = lookup(each.value, "cost_types", null) != null ? [each.value.cost_types] : []

content {
include_credit = lookup(cost_types.value, "include_credit", null)
include_discount = lookup(cost_types.value, "include_discount", null)
include_other_subscription = lookup(cost_types.value, "include_other_subscription", null)
include_recurring = lookup(cost_types.value, "include_recurring", null)
include_refund = lookup(cost_types.value, "include_refund", null)
include_subscription = lookup(cost_types.value, "include_subscription", null)
include_support = lookup(cost_types.value, "include_support", null)
include_tax = lookup(cost_types.value, "include_tax", null)
include_upfront = lookup(cost_types.value, "include_upfront", null)
use_blended = lookup(cost_types.value, "use_blended", null)
}
}

dynamic "cost_filter" {
for_each = toset(each.value.cost_filter)

content {
name = cost_filter.value.name
values = cost_filter.value.values
}
}

dynamic "notification" {
for_each = var.notifications != null ? toset(var.notifications) : []

content {
comparison_operator = notification.value.comparison_operator
notification_type = notification.value.notification_type
threshold = notification.value.threshold
threshold_type = notification.value.threshold_type
subscriber_email_addresses = notification.value.subscriber_email_addresses
subscriber_sns_topic_arns = concat(
var.sns_topic_name != null && length(var.notifications) > 0 ? [aws_sns_topic.notifications[0].arn] : [],
notification.value.subscriber_sns_topic_arns,
)
}
}

tags = merge(
var.tags_all,
each.value.tags
)
}

resource "aws_sns_topic" "notifications" {
count = var.sns_topic_name != null && length(var.notifications) > 0 ? 1 : 0
name = var.sns_topic_name
tags = var.tags_all
}

resource "aws_sns_topic_policy" "notifications" {
count = var.sns_topic_name != null && length(var.notifications) > 0 ? 1 : 0

arn = aws_sns_topic.notifications[0].arn
policy = data.aws_iam_policy_document.notifications[0].json
}

data "aws_iam_policy_document" "notifications" {
count = var.sns_topic_name != null && length(var.notifications) > 0 ? 1 : 0

statement {
sid = "__default_statement_ID"
effect = "Allow"
actions = [
"SNS:GetTopicAttributes",
"SNS:SetTopicAttributes",
"SNS:AddPermission",
"SNS:RemovePermission",
"SNS:DeleteTopic",
"SNS:Subscribe",
"SNS:ListSubscriptionsByTopic",
"SNS:Publish",
]
resources = [
aws_sns_topic.notifications[0].arn,
]
principals {
type = "AWS"
identifiers = ["*"]
}

condition {
test = "StringEquals"
variable = "AWS:SourceOwner"
values = [
data.aws_caller_identity.current.account_id,
]
}
}

statement {
sid = "AllowAWSBudgetsPublish"
effect = "Allow"
actions = [
"sns:Publish",
]
resources = [
aws_sns_topic.notifications[0].arn,
]
principals {
type = "Service"
identifiers = ["budgets.amazonaws.com"]
}
condition {
test = "StringEquals"
variable = "AWS:SourceAccount"
values = [
data.aws_caller_identity.current.account_id,
]
}
condition {
test = "ArnLike"
variable = "aws:SourceArn"
values = [
"arn:aws:budgets::${data.aws_caller_identity.current.account_id}:*",
]
}
}
}
4 changes: 4 additions & 0 deletions modules/aws/cost-control/budgets/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
output "sns_topic_arn" {
description = "The ARN of the SNS topic to use for budget notifications"
value = var.sns_topic_name != null && length(var.notifications) > 0 ? aws_sns_topic.notifications[0].arn : null
}
65 changes: 65 additions & 0 deletions modules/aws/cost-control/budgets/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
variable "budgets" {
description = "A map of budgets to create"
type = map(object({
budget_type = optional(string, "COST")
limit_amount = number
limit_unit = optional(string, "USD")
time_period_start = optional(string, null)
time_period_end = optional(string, null)
time_unit = optional(string, "MONTHLY")
cost_types = optional(object({
include_credit = optional(bool, false)
include_discount = optional(bool, false)
include_other_subscription = optional(bool, false)
include_recurring = optional(bool, false)
include_refund = optional(bool, false)
include_subscription = optional(bool, false)
include_support = optional(bool, false)
include_tax = optional(bool, false)
include_upfront = optional(bool, false)
use_blended = optional(bool, false)
}), {
include_credit = false
include_discount = false
include_other_subscription = false
include_recurring = false
include_refund = false
include_subscription = true
include_support = false
include_tax = false
include_upfront = false
use_blended = false
})
cost_filter = list(object({
name = string
values = list(string)
}))
tags = optional(map(string), {})
}))
default = {}
}

variable "sns_topic_name" {
description = "The name of the SNS topic to create for budget notifications"
type = string
default = "budget-notifications"
}

variable "notifications" {
description = "A list of notifications to create for budgets"
type = list(object({
comparison_operator = optional(string, "GREATER_THAN")
notification_type = optional(string, "ACTUAL")
threshold = optional(number, 100)
threshold_type = optional(string, "PERCENTAGE")
subscriber_email_addresses = optional(list(string), [])
subscriber_sns_topic_arns = optional(list(string), [])
}))
default = []
}

variable "tags_all" {
type = map(string)
description = "A map of tags to assign to all resources"
default = {}
}
10 changes: 10 additions & 0 deletions modules/aws/cost-control/budgets/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
terraform {
required_version = ">=1.3"

required_providers {
aws = {
source = "hashicorp/aws"
version = ">=4.0"
}
}
}