Skip to content

Commit d437347

Browse files
author
Steven Nemetz
committed
first commit
0 parents  commit d437347

21 files changed

+627
-0
lines changed

.circleci/config.yml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
version: 2
2+
3+
jobs:
4+
build:
5+
docker:
6+
- image: hashicorp/terraform:0.11.3
7+
entrypoint: /bin/sh
8+
steps:
9+
- checkout
10+
- run:
11+
name: "Validate tf files (terraform validate)"
12+
command: |
13+
find . -type f -name "*.tf" -exec dirname {} \;|sort -u | while read m; do (terraform validate -check-variables=false "$m" && echo "√ $m") || exit 1 ; done
14+
- run:
15+
name: "Check: Terraform formatting (terraform fmt)"
16+
command: |
17+
if [ `terraform fmt --list=true -diff=true -write=false | tee format-issues | wc -c` -ne 0 ]; then
18+
echo "Some terraform files need be formatted, run 'terraform fmt' to fix"
19+
echo "Formatting issues:"
20+
cat format-issues
21+
exit 1
22+
fi
23+
- run:
24+
name: "Install: tflint"
25+
command: |
26+
apk add jq wget
27+
# Get latest version of tflint
28+
pkg_arch=linux_amd64
29+
dl_url=$(curl -s https://api.github.com/repos/wata727/tflint/releases/latest | jq -r ".assets[] | select(.name | test(\"${pkg_arch}\")) | .browser_download_url")
30+
wget ${dl_url}
31+
unzip tflint_linux_amd64.zip
32+
mkdir -p /usr/local/tflint/bin
33+
# Setup PATH for later run steps - ONLY for Bash and not in Bash
34+
#echo 'export PATH=/usr/local/tflint/bin:$PATH' >> $BASH_ENV
35+
echo "Installing tflint..."
36+
install tflint /usr/local/tflint/bin
37+
echo "Configuring tflint..."
38+
tf_ver=$(terraform version | awk 'FNR <= 1' | cut -dv -f2)
39+
echo -e "\tConfig for terraform version: ${tf_ver}"
40+
if [ -f '.tflint.hcl' ]; then
41+
sed -i "/terraform_version =/s/\".*\"/\"${tf_ver}\"/" .tflint.hcl
42+
else
43+
{
44+
echo -e "config {\nterraform_version = \"${tf_ver}\"\ndeep_check = true\nignore_module = {"
45+
for module in $(grep -h '[^a-zA-Z]source[ =]' *.tf | sed -r 's/.*=\s+//' | sort -u); do
46+
# if not ^"../
47+
echo "${module} = true"
48+
done
49+
echo "}}"
50+
} > .tflint.hcl
51+
fi
52+
echo "tflint configuration:"
53+
cat .tflint.hcl
54+
- run:
55+
# Not supporting modules from registry ?? v0.5.4
56+
# For now, must ignore in config file
57+
name: "Check: tflint"
58+
command: |
59+
#echo "Initializing terraform..."
60+
#terraform init -input=false
61+
echo "Running tflint..."
62+
/usr/local/tflint/bin/tflint --version
63+
/usr/local/tflint/bin/tflint
64+
65+
workflows:
66+
version: 2
67+
build:
68+
jobs:
69+
- build

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*.tfstate
2+
*.tfstate.backup
3+
.terraform

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# terraform-aws-budget
2+
3+
Terraform module to setup AWS budgets and notifications
4+
5+
This is fairly specific for now. It can create budgets for multiple linked accounts. Each with it's own limit.
6+
Notification is only supported for a single email address
7+
8+
NOTE: Setting up notification and subscribers is not currently supported in Terraform
9+
10+
Notifications are predefined in this module. They are:
11+
- 110% of forecasted
12+
- 80% of actual
13+
- 90% of actual

bugs-to-report.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Issues budgets resource:
2+
3+
Why start date required? AWS doc says it is optional and has rules of how it is created when not provided
4+
Work around: Calculate start of month and provide
5+
6+
When account id is not provided get "Account min size is 12". Appears provider is not providing it properly
7+
Work around: provide account id
8+
9+
Issue with changing budget name. This should force the budget to be recreated. AWS doesn't allow changing the name
10+
Current generates error: Failed to call UpdateBudget - Unable to get budget: BUDGET_NEW_NAME - the budget doesn't exist.

examples/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
# Example and manual test cases
3+
4+
Each directory contains a configuration that serves as a manual test case and an example
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Example: Annually account budgets
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
module "budgets" {
3+
source = "../../"
4+
budgets = "${var.budgets}"
5+
email = "snemetz@wiser.com"
6+
time_unit = "ANNUALLY"
7+
}
Lines changed: 10 additions & 0 deletions
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+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
variable "environment" {
2+
default = "test"
3+
}
4+
5+
variable "region" {
6+
default = "us-west-2"
7+
}
8+
9+
variable "budgets" {
10+
description = "List of account budget maps. Each map contains: name (name of budget), account (AWS linked account ID), and limit (budget limit)"
11+
12+
default = [
13+
{
14+
name = "acc-1"
15+
account = "123456789011"
16+
limit = 100
17+
},
18+
{
19+
name = "acc-2"
20+
account = "123456789012"
21+
limit = 200
22+
},
23+
{
24+
name = "acc-3"
25+
account = "123456789013"
26+
limit = 300
27+
},
28+
]
29+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Example: Quarterly account budgets
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
module "budgets" {
3+
source = "../../"
4+
budgets = "${var.budgets}"
5+
email = "snemetz@wiser.com"
6+
time_unit = "QUARTERLY"
7+
}
Lines changed: 10 additions & 0 deletions
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+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
variable "environment" {
2+
default = "test"
3+
}
4+
5+
variable "region" {
6+
default = "us-west-2"
7+
}
8+
9+
variable "budgets" {
10+
description = "List of account budget maps. Each map contains: name (name of budget), account (AWS linked account ID), and limit (budget limit)"
11+
12+
default = [
13+
{
14+
name = "acc-1"
15+
account = "123456789011"
16+
limit = 100
17+
},
18+
{
19+
name = "acc-2"
20+
account = "123456789012"
21+
limit = 200
22+
},
23+
{
24+
name = "acc-3"
25+
account = "123456789013"
26+
limit = 300
27+
},
28+
]
29+
}

files/sns_policy.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"Id": "MyBudgetPolicy",
3+
"Version": "2012-10-17",
4+
"Statement": [
5+
{
6+
"Sid": "Mystatementid",
7+
"Effect": "Allow",
8+
"Principal": {
9+
"Service": "budgets.amazonaws.com"
10+
},
11+
"Action": [
12+
"SNS:Publish",
13+
"SNS:RemovePermission",
14+
"SNS:SetTopicAttributes",
15+
"SNS:DeleteTopic",
16+
"SNS:ListSubscriptionsByTopic",
17+
"SNS:GetTopicAttributes",
18+
"SNS:Receive",
19+
"SNS:AddPermission",
20+
"SNS:Subscribe"
21+
],
22+
"Resource": "*"
23+
}
24+
]
25+
}

files/sns_template.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"AWSTemplateFormatVersion": "2010-09-09",
3+
"Description": "Best Practice SNS Topic",
4+
"Parameters": {
5+
"SNSTopic": {
6+
"Type": "String",
7+
"Description": "The ARN of the SNS topic"
8+
},
9+
"SubscriptionEndPoint": {
10+
"Type": "String",
11+
"Description": "The endpoint that receives notifications from the Amazon SNS topic. The endpoint value depends on the protocol that you specify. This could be a URL or ARN"
12+
},
13+
"SubscriptionProtocol": {
14+
"Type": "String",
15+
"Description": "The subscription's protocol",
16+
"AllowedValues": [
17+
"http",
18+
"https",
19+
"email",
20+
"email-json",
21+
"sms",
22+
"sqs",
23+
"application",
24+
"lambda"
25+
],
26+
"Default": "email"
27+
}
28+
},
29+
"Resources": {
30+
"SNSSubscription": {
31+
"Type": "AWS::SNS::Subscription",
32+
"Properties": {
33+
"Endpoint": {
34+
"Ref": "SubscriptionEndPoint"
35+
},
36+
"Protocol": {
37+
"Ref": "SubscriptionProtocol"
38+
},
39+
"TopicArn": {
40+
"Ref": "SNSTopic"
41+
}
42+
}
43+
}
44+
}
45+
}

main.tf

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
data "aws_caller_identity" "current" {}
2+
3+
# Activate tags for cost allocation tracking
4+
# Must be done by master account
5+
6+
# https://docs.aws.amazon.com/aws-cost-management/latest/APIReference/API_budgets_CostTypes.html
7+
#
8+
/*
9+
# This is future work for single budget that can set most things
10+
locals {
11+
cost_filters = {
12+
AZ = {
13+
AZ = ""
14+
}
15+
LinkedAccount = {
16+
LinkedAccount = "${join(",", var.account_ids)}"
17+
}
18+
Operation = {
19+
Operation = ""
20+
}
21+
PurchaseType = {
22+
PurchaseType = ""
23+
}
24+
Service = {
25+
Service = ""
26+
}
27+
TagKeyValue = {
28+
TagKeyValue = "Stack$$${var.service}"
29+
}
30+
UsageType = {
31+
UsageType = ""
32+
}
33+
}
34+
}
35+
/**/
36+
resource "aws_budgets_budget" "budgets" {
37+
count = "${length(var.budgets)}"
38+
account_id = "${data.aws_caller_identity.current.account_id}"
39+
name = "${var.budget_name_prefix}${lookup(var.budgets[count.index], "name")}-${title(lower(var.time_unit))}"
40+
budget_type = "${var.budget_type}"
41+
limit_unit = "${var.limit_unit}"
42+
time_unit = "${var.time_unit}"
43+
44+
limit_amount = "${var.time_unit == "ANNUALLY" ?
45+
lookup(var.budgets[count.index], "limit", "100") * 12 :
46+
var.time_unit == "QUARTERLY" ?
47+
lookup(var.budgets[count.index], "limit", "100") * 3 :
48+
lookup(var.budgets[count.index], "limit", "100")
49+
}"
50+
51+
time_period_start = "${var.time_unit == "ANNUALLY" ?
52+
"${substr(timestamp(), 0, 5)}01-01_00:00" :
53+
var.time_unit == "QUARTERLY" ?
54+
"${substr(timestamp(), 0, 5)}${format("%02d", (substr(timestamp(), 5, 2) - 1) / 3 * 3 + 1)}-01_00:00" :
55+
"${substr(timestamp(), 0, 8)}01_00:00"
56+
}"
57+
58+
cost_filters = {
59+
LinkedAccount = "${lookup(var.budgets[count.index], "account")}"
60+
}
61+
62+
lifecycle {
63+
ignore_changes = ["time_period_start"]
64+
}
65+
}
66+
67+
# Attributes: id
68+

0 commit comments

Comments
 (0)