├── examples ├── complete │ ├── variables.tf │ ├── versions.tf │ ├── main.tf │ ├── outputs.tf │ └── README.md ├── pricing-dev │ ├── variables.tf │ ├── versions.tf │ ├── main.tf │ ├── outputs.tf │ └── README.md ├── cost-modules-tf │ ├── variables.tf │ ├── versions.tf │ ├── outputs.tf │ ├── main.tf │ └── README.md ├── fixtures │ ├── all-resources │ │ ├── outputs.tf │ │ ├── variables.tf │ │ ├── versions.tf │ │ ├── update_fixture.sh │ │ ├── data.tf │ │ └── main.tf │ ├── combinations │ │ ├── outputs.tf │ │ ├── variables.tf │ │ ├── .gitignore │ │ ├── versions.tf │ │ ├── README.md │ │ ├── update_fixture.sh │ │ └── main.tf │ └── etc │ │ ├── .gitignore │ │ ├── ebs-terraform-state.json │ │ ├── plan-ebs-volume.json │ │ ├── plan-instance-with-multiple-ebs.json │ │ ├── plan-ebs.json │ │ └── plan-lb-and-nat.json ├── pricing-resources │ ├── variables.tf │ ├── versions.tf │ ├── main.tf │ ├── outputs.tf │ └── README.md └── pricing-terraform-state-and-plan │ ├── variables.tf │ ├── versions.tf │ ├── main.tf │ ├── outputs.tf │ └── README.md ├── modules ├── pricing │ ├── .envrc │ ├── versions.tf │ ├── debug_filters.tf │ ├── regions.tf │ ├── local_dev.tf │ ├── outputs.tf │ ├── variables.tf │ ├── main.tf │ ├── README.md │ ├── state_and_plan_content_from_json.tf │ └── filters.tf └── cost.modules.tf │ ├── versions.tf │ ├── outputs.tf │ ├── variables.tf │ ├── main.tf │ └── README.md ├── .editorconfig ├── .gitignore ├── .github └── workflows │ ├── release.yml │ ├── lock.yml │ ├── stale-actions.yaml │ ├── pr-title.yml │ └── pre-commit.yml ├── .releaserc.json ├── .pre-commit-config.yaml ├── dev ├── calculate_results.sh └── aws_pricing.md ├── CHANGELOG.md ├── README.md └── LICENSE /examples/complete/variables.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/pricing-dev/variables.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/cost-modules-tf/variables.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/fixtures/all-resources/outputs.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/fixtures/combinations/outputs.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/fixtures/combinations/variables.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/pricing-resources/variables.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/fixtures/all-resources/variables.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/fixtures/etc/.gitignore: -------------------------------------------------------------------------------- 1 | !*.tfstate 2 | -------------------------------------------------------------------------------- /examples/fixtures/combinations/.gitignore: -------------------------------------------------------------------------------- 1 | !*.tfstate 2 | -------------------------------------------------------------------------------- /examples/pricing-terraform-state-and-plan/variables.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /modules/pricing/.envrc: -------------------------------------------------------------------------------- 1 | # Using these variables when working on the module locally. There is no `provider "aws"` block in the module. 2 | export AWS_REGION=us-east-1 3 | -------------------------------------------------------------------------------- /modules/pricing/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 4.0" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/pricing-resources/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 4.0" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/fixtures/all-resources/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 5.0" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/fixtures/combinations/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 4.0" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/fixtures/combinations/README.md: -------------------------------------------------------------------------------- 1 | # Combinations 2 | 3 | This directory contains configurations for various combinations of resources and modules with all types of meta arguments (count, for_each). 4 | 5 | Creates: plan and state file. 6 | -------------------------------------------------------------------------------- /examples/complete/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 4.0" 8 | } 9 | 10 | local = { 11 | source = "hashicorp/local" 12 | version = ">= 1.0" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/pricing-dev/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 4.0" 8 | } 9 | 10 | local = { 11 | source = "hashicorp/local" 12 | version = ">= 1.0" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/cost-modules-tf/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 4.0" 8 | } 9 | 10 | local = { 11 | source = "hashicorp/local" 12 | version = ">= 1.0" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /modules/cost.modules.tf/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | null = { 6 | source = "hashicorp/null" 7 | version = ">= 2.0" 8 | } 9 | 10 | local = { 11 | source = "hashicorp/local" 12 | version = ">= 1.0" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/pricing-terraform-state-and-plan/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 4.0" 8 | } 9 | 10 | local = { 11 | source = "hashicorp/local" 12 | version = ">= 1.0" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/fixtures/all-resources/update_fixture.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e # do exit on bad exit code 4 | 5 | # Locate the directory in which this script is located 6 | readonly SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 7 | 8 | cd $SCRIPT_PATH 9 | 10 | terraform init -input=false 11 | terraform plan -out=plan.tfplan > /dev/null && terraform show -json plan.tfplan | jq -r > plan.json 12 | -------------------------------------------------------------------------------- /examples/cost-modules-tf/outputs.tf: -------------------------------------------------------------------------------- 1 | output "price_tfstate" { 2 | description = "Costs for local file - state" 3 | value = module.tfstate.costs 4 | } 5 | 6 | output "price_tfstate_with_terraform_remote_state" { 7 | description = "Costs for remote state" 8 | value = module.tfstate_with_terraform_remote_state.costs 9 | } 10 | 11 | output "price_plan" { 12 | description = "Costs for local file - plan" 13 | value = module.plan.costs 14 | } 15 | -------------------------------------------------------------------------------- /examples/fixtures/combinations/update_fixture.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e # do exit on bad exit code 4 | 5 | # Locate the directory in which this script is located 6 | readonly SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 7 | 8 | cd $SCRIPT_PATH 9 | 10 | terraform init -input=false 11 | terraform plan -out=plan.tfplan > /dev/null && terraform show -json plan.tfplan | jq -r > plan.json 12 | 13 | # Apply to create terraform.tfstate 14 | terraform apply plan.tfplan 15 | -------------------------------------------------------------------------------- /modules/cost.modules.tf/outputs.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | costs = var.enabled ? jsondecode(file(data.null_data_source.costs[0].outputs["cost_filename"])) : tomap({}) 3 | } 4 | 5 | output "costs" { 6 | description = "Total costs" 7 | value = local.costs 8 | } 9 | 10 | output "hourly" { 11 | description = "Hourly costs" 12 | value = lookup(local.costs, "hourly", 0) 13 | } 14 | 15 | output "monthly" { 16 | description = "Monthly costs" 17 | value = lookup(local.costs, "monthly", 0) 18 | } 19 | -------------------------------------------------------------------------------- /examples/pricing-resources/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "us-east-1" 3 | } 4 | 5 | module "pricing" { 6 | source = "../../modules/pricing" 7 | 8 | debug_output = true 9 | query_all_regions = false 10 | 11 | resources = { 12 | "aws_instance.this#3" = { # 3 instances 13 | instanceType = "c5.xlarge" 14 | location = "eu-west-2" 15 | } 16 | "aws_instance.this2" = { 17 | instanceType = "t3.large" 18 | location = "me-central-1" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /modules/pricing/debug_filters.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | # Filters 3 | aws_cli_filters = { 4 | for k, v in local.pricing_product_filters : 5 | k => join(" ", [ 6 | for f_k, f_v in v.filters : format("Type=TERM_MATCH,Field=%s,Value=\"%s\"", f_k, f_v == null ? "null" : f_v) 7 | ]) 8 | } 9 | 10 | aws_cli_commands = { 11 | for k, v in local.pricing_product_filters : 12 | k => < v if !contains(["ap-northeast-3"], k) } 8 | 9 | name = each.value 10 | } 11 | 12 | locals { 13 | # Note: Adding more regions and local zones. Copied from https://github.com/powdahound/ec2instances.info/blob/master/ec2.py 14 | all_aws_regions = merge({ for k, v in data.aws_region.one : replace(v.description, "Europe", "EU") => k }, { 15 | "Asia Pacific (Osaka-Local)" = "ap-northeast-3" 16 | "US West (Los Angeles)" = "us-west-2" 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /examples/fixtures/all-resources/data.tf: -------------------------------------------------------------------------------- 1 | ################################################################## 2 | # Data sources to get VPC, subnet, security group and AMI details 3 | ################################################################## 4 | 5 | data "aws_vpc" "default" { 6 | default = true 7 | } 8 | 9 | data "aws_subnets" "all" { 10 | filter { 11 | name = "vpc-id" 12 | values = [data.aws_vpc.default.id] 13 | } 14 | } 15 | 16 | data "aws_ami" "amazon_linux" { 17 | most_recent = true 18 | owners = ["amazon"] 19 | 20 | filter { 21 | name = "name" 22 | values = ["amzn-ami-hvm-*-x86_64-gp2"] 23 | } 24 | 25 | filter { 26 | name = "owner-alias" 27 | values = ["amazon"] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/complete/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "us-east-1" 3 | } 4 | 5 | module "pricing" { 6 | source = "../../modules/pricing" 7 | 8 | debug_output = true 9 | 10 | aws_default_region = "eu-west-1" 11 | 12 | content = jsondecode(data.local_file.local_plan.content) 13 | 14 | resources = { 15 | "aws_instance.this#3" = { # 3 instances 16 | instanceType = "c5.xlarge" 17 | location = "eu-west-2" 18 | } 19 | "aws_instance.this2" = { 20 | instanceType = "c4.xlarge" 21 | location = "eu-central-1" 22 | } 23 | } 24 | } 25 | 26 | ########################### 27 | # Terraform plan (as JSON) 28 | ########################### 29 | data "local_file" "local_plan" { 30 | filename = "../fixtures/etc/ec2.terraform.tfstate" 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # Terraform lockfile 5 | .terraform.lock.hcl 6 | 7 | # .tfstate files 8 | *.tfstate 9 | *.tfstate.* 10 | 11 | # Crash log files 12 | crash.log 13 | 14 | # Exclude all .tfvars files, which are likely to contain sentitive data, such as 15 | # password, private keys, and other secrets. These should not be part of version 16 | # control as they are data points which are potentially sensitive and subject 17 | # to change depending on the environment. 18 | *.tfvars 19 | 20 | # Ignore override files as they are usually used to override resources locally and so 21 | # are not checked in 22 | override.tf 23 | override.tf.json 24 | *_override.tf 25 | *_override.tf.json 26 | 27 | # Ignore CLI configuration files 28 | .terraformrc 29 | terraform.rc 30 | /tmp 31 | -------------------------------------------------------------------------------- /modules/cost.modules.tf/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | filename = format("%s/file_%s.json", var.tmp_dir, md5(format("%s%s", var.content, var.filename_hash))) 3 | cost_filename = format("%s/cost_%s.json", var.tmp_dir, md5(format("%s%s", var.content, var.filename_hash))) 4 | } 5 | 6 | resource "local_file" "json_input" { 7 | count = var.enabled ? 1 : 0 8 | 9 | filename = local.filename 10 | content = var.content 11 | } 12 | 13 | resource "null_resource" "curl" { 14 | count = var.enabled ? 1 : 0 15 | 16 | triggers = { 17 | file = local_file.json_input[0].filename 18 | } 19 | 20 | provisioner "local-exec" { 21 | command = format("curl -s -X POST -H \"Content-Type: application/json\" -d @%s -o %s https://cost.modules.tf/", local_file.json_input[0].filename, local.cost_filename) 22 | } 23 | } 24 | 25 | data "null_data_source" "costs" { 26 | count = var.enabled ? 1 : 0 27 | 28 | inputs = { 29 | id = null_resource.curl[0].id 30 | cost_filename = local.cost_filename 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | - master 9 | paths: 10 | - '**/*.tpl' 11 | - '**/*.py' 12 | - '**/*.tf' 13 | - '.github/workflows/release.yml' 14 | 15 | jobs: 16 | release: 17 | name: Release 18 | runs-on: ubuntu-latest 19 | # Skip running release workflow on forks 20 | if: github.repository_owner == 'terraform-aws-modules' 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | with: 25 | persist-credentials: false 26 | fetch-depth: 0 27 | 28 | - name: Release 29 | uses: cycjimmy/semantic-release-action@v4 30 | with: 31 | semantic_version: 23.0.2 32 | extra_plugins: | 33 | @semantic-release/changelog@6.0.3 34 | @semantic-release/git@10.0.1 35 | conventional-changelog-conventionalcommits@7.0.2 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} 38 | -------------------------------------------------------------------------------- /examples/cost-modules-tf/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-west-1" 3 | } 4 | 5 | ################## 6 | # Terraform state 7 | ################## 8 | 9 | data "local_file" "tfstate" { 10 | filename = "../fixtures/etc/ec2.terraform.tfstate" 11 | } 12 | 13 | module "tfstate" { 14 | source = "../../modules/cost.modules.tf" 15 | 16 | content = data.local_file.tfstate.content 17 | } 18 | 19 | data "terraform_remote_state" "local_state" { 20 | backend = "local" 21 | 22 | config = { 23 | path = "../fixtures/etc/ec2.terraform.tfstate" 24 | } 25 | } 26 | 27 | module "tfstate_with_terraform_remote_state" { 28 | source = "../../modules/cost.modules.tf" 29 | 30 | content = file(data.terraform_remote_state.local_state.config.path) 31 | filename_hash = "something" 32 | } 33 | 34 | ################# 35 | # Terraform plan 36 | ################# 37 | 38 | data "local_file" "plan" { 39 | filename = "../../examples/fixtures/all-resources/plan.json" 40 | } 41 | 42 | module "plan" { 43 | source = "../../modules/cost.modules.tf" 44 | 45 | content = data.local_file.plan.content 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/lock.yml: -------------------------------------------------------------------------------- 1 | name: 'Lock Threads' 2 | 3 | on: 4 | schedule: 5 | - cron: '50 1 * * *' 6 | 7 | jobs: 8 | lock: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: dessant/lock-threads@v5 12 | with: 13 | github-token: ${{ secrets.GITHUB_TOKEN }} 14 | issue-comment: > 15 | I'm going to lock this issue because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues. 16 | If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further. 17 | issue-inactive-days: '30' 18 | pr-comment: > 19 | I'm going to lock this pull request because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues. 20 | If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further. 21 | pr-inactive-days: '30' 22 | -------------------------------------------------------------------------------- /examples/pricing-dev/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "us-east-1" 3 | } 4 | 5 | ########################### 6 | # Terraform plan (as JSON) 7 | ########################### 8 | data "local_file" "local_plan" { 9 | filename = "../../examples/fixtures/all-resources/plan.json" 10 | # filename = "../fixtures/ec2-various/ebs-terraform-state.json" 11 | # filename = "../fixtures/ec2-various/plan-lb-and-nat.json" 12 | } 13 | 14 | module "pricing" { 15 | source = "../../modules/pricing" 16 | 17 | debug_output = true 18 | call_aws_pricing_api = false 19 | 20 | aws_default_region = "af-south-1" 21 | 22 | content = jsondecode(data.local_file.local_plan.content) 23 | } 24 | 25 | ####### 26 | # Run aws_cli response to check results by running ../../dev/calculate_results.sh 27 | ####### 28 | #locals { 29 | # run_aws_cli_commands = false 30 | #} 31 | # 32 | #resource "null_resource" "aws_cli" { 33 | # for_each = local.run_aws_cli_commands ? module.pricing.aws_cli_commands : {} 34 | # 35 | # triggers = { 36 | # trigger = timestamp() 37 | # } 38 | # 39 | # provisioner "local-exec" { 40 | # command = each.value 41 | # } 42 | #} 43 | -------------------------------------------------------------------------------- /examples/pricing-resources/outputs.tf: -------------------------------------------------------------------------------- 1 | output "resources" { 2 | description = "Map of provided resources with filters" 3 | value = module.pricing.resources 4 | } 5 | 6 | output "input_resources" { 7 | description = "Map of input resource filters (from plan/state or static)" 8 | value = module.pricing.input_resources 9 | } 10 | 11 | output "pricing_product_filters" { 12 | description = "Map of pricing product filters (as they are submitted using data source `aws_pricing_product`)" 13 | value = module.pricing.pricing_product_filters 14 | } 15 | 16 | output "resource_quantity" { 17 | description = "Map of resource quantity" 18 | value = module.pricing.resource_quantity 19 | } 20 | 21 | output "pricing_per_resources" { 22 | description = "Map of resource pricing" 23 | value = module.pricing.pricing_per_resources 24 | } 25 | 26 | output "total_price_per_hour" { 27 | description = "Total price for all resources" 28 | value = module.pricing.total_price_per_hour 29 | } 30 | 31 | output "total_price_per_month" { 32 | description = "Total price for all resources per month (730 hours)" 33 | value = module.pricing.total_price_per_month 34 | } 35 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "main", 4 | "master" 5 | ], 6 | "ci": false, 7 | "plugins": [ 8 | [ 9 | "@semantic-release/commit-analyzer", 10 | { 11 | "preset": "conventionalcommits" 12 | } 13 | ], 14 | [ 15 | "@semantic-release/release-notes-generator", 16 | { 17 | "preset": "conventionalcommits" 18 | } 19 | ], 20 | [ 21 | "@semantic-release/github", 22 | { 23 | "successComment": "This ${issue.pull_request ? 'PR is included' : 'issue has been resolved'} in version ${nextRelease.version} :tada:", 24 | "labels": false, 25 | "releasedLabels": false 26 | } 27 | ], 28 | [ 29 | "@semantic-release/changelog", 30 | { 31 | "changelogFile": "CHANGELOG.md", 32 | "changelogTitle": "# Changelog\n\nAll notable changes to this project will be documented in this file." 33 | } 34 | ], 35 | [ 36 | "@semantic-release/git", 37 | { 38 | "assets": [ 39 | "CHANGELOG.md" 40 | ], 41 | "message": "chore(release): version ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 42 | } 43 | ] 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /examples/complete/outputs.tf: -------------------------------------------------------------------------------- 1 | output "pricing_resources" { 2 | description = "Map of provided resources with filters" 3 | value = module.pricing.resources 4 | } 5 | 6 | output "pricing_input_resources" { 7 | description = "Map of input resource filters (from plan/state or static)" 8 | value = module.pricing.input_resources 9 | } 10 | 11 | output "pricing_pricing_product_filters" { 12 | description = "Map of pricing product filters (as they are submitted using data source `aws_pricing_product`)" 13 | value = module.pricing.pricing_product_filters 14 | } 15 | 16 | output "pricing_resource_quantity" { 17 | description = "Map of resource quantity" 18 | value = module.pricing.resource_quantity 19 | } 20 | 21 | output "pricing_pricing_per_resources" { 22 | description = "Map of resource pricing" 23 | value = module.pricing.pricing_per_resources 24 | } 25 | 26 | output "pricing_total_price_per_hour" { 27 | description = "Total price for all resources" 28 | value = module.pricing.total_price_per_hour 29 | } 30 | 31 | output "pricing_total_price_per_month" { 32 | description = "Total price for all resources per month (730 hours)" 33 | value = module.pricing.total_price_per_month 34 | } 35 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/antonbabenko/pre-commit-terraform 3 | rev: v1.96.1 4 | hooks: 5 | - id: terraform_fmt 6 | - id: terraform_docs 7 | args: 8 | - '--args=--lockfile=false' 9 | - id: terraform_tflint 10 | args: 11 | - '--args=--only=terraform_deprecated_interpolation' 12 | - '--args=--only=terraform_deprecated_index' 13 | - '--args=--only=terraform_unused_declarations' 14 | - '--args=--only=terraform_comment_syntax' 15 | - '--args=--only=terraform_documented_outputs' 16 | - '--args=--only=terraform_documented_variables' 17 | - '--args=--only=terraform_typed_variables' 18 | - '--args=--only=terraform_module_pinned_source' 19 | - '--args=--only=terraform_naming_convention' 20 | - '--args=--only=terraform_required_version' 21 | - '--args=--only=terraform_required_providers' 22 | - '--args=--only=terraform_standard_module_structure' 23 | - '--args=--only=terraform_workspace_remote' 24 | - id: terraform_validate 25 | - repo: https://github.com/pre-commit/pre-commit-hooks 26 | rev: v5.0.0 27 | hooks: 28 | - id: check-merge-conflict 29 | - id: end-of-file-fixer 30 | - id: trailing-whitespace 31 | -------------------------------------------------------------------------------- /dev/calculate_results.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Run all AWS CLI commands and verify that 1 result is returned 4 | # Make sure to run: terraform apply (with `local.debug_output = true` to populate aws_cli_commands) 5 | 6 | set +e # do not exit on bad exit code 7 | 8 | aws_cli_commands=$(terraform output -json aws_cli_commands | jq -r '.[] | gsub("[\\n\\t]"; "")') 9 | 10 | while IFS=$'\n' read -ra commands; do 11 | for one in "${commands[@]}"; do 12 | printf '%*s\n' 58 | tr ' ' '-' 13 | echo $one 14 | result=$(/bin/bash -c "$one") 15 | exit_code=$? 16 | 17 | if [ $exit_code -ne 0 ]; then 18 | echo "Error!" 19 | continue 20 | fi 21 | 22 | products=$(echo $result | jq -r '.PriceList | length') 23 | 24 | if [ $products -eq 0 ]; then 25 | echo "0 products found. Expected 1!" 26 | elif [ $products -ne 1 ]; then 27 | echo "$products products found. Expected 1!" 28 | echo $result | jq -r '.PriceList[0] | fromjson | .product' 29 | echo $result | jq -r '.PriceList[1] | fromjson | .product' 30 | else 31 | echo "OK!" 32 | echo $result | jq -r '.PriceList[0] | fromjson | .terms.OnDemand[].priceDimensions[].pricePerUnit.USD' 33 | echo $result | jq -r '.PriceList[0] | fromjson | .product' 34 | fi 35 | 36 | done 37 | done <<< "$aws_cli_commands" 38 | -------------------------------------------------------------------------------- /examples/pricing-dev/outputs.tf: -------------------------------------------------------------------------------- 1 | output "aws_cli_commands" { 2 | description = "List of AWS CLI commands with Pricing filters" 3 | value = module.pricing.aws_cli_commands 4 | } 5 | 6 | ####### 7 | 8 | output "pricing_resources" { 9 | description = "Map of provided resources with filters" 10 | value = module.pricing.resources 11 | } 12 | 13 | output "pricing_input_resources" { 14 | description = "Map of input resource filters (from plan/state or static)" 15 | value = module.pricing.input_resources 16 | } 17 | 18 | output "pricing_pricing_product_filters" { 19 | description = "Map of pricing product filters (as they are submitted using data source `aws_pricing_product`)" 20 | value = module.pricing.pricing_product_filters 21 | } 22 | 23 | output "pricing_resource_quantity" { 24 | description = "Map of resource quantity" 25 | value = module.pricing.resource_quantity 26 | } 27 | 28 | output "pricing_pricing_per_resources" { 29 | description = "Map of resource pricing" 30 | value = module.pricing.pricing_per_resources 31 | } 32 | 33 | output "pricing_total_price_per_hour" { 34 | description = "Total price for all resources" 35 | value = module.pricing.total_price_per_hour 36 | } 37 | 38 | output "pricing_total_price_per_month" { 39 | description = "Total price for all resources per month (730 hours)" 40 | value = module.pricing.total_price_per_month 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/stale-actions.yaml: -------------------------------------------------------------------------------- 1 | name: 'Mark or close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v9 11 | with: 12 | repo-token: ${{ secrets.GITHUB_TOKEN }} 13 | # Staling issues and PR's 14 | days-before-stale: 30 15 | stale-issue-label: stale 16 | stale-pr-label: stale 17 | stale-issue-message: | 18 | This issue has been automatically marked as stale because it has been open 30 days 19 | with no activity. Remove stale label or comment or this issue will be closed in 10 days 20 | stale-pr-message: | 21 | This PR has been automatically marked as stale because it has been open 30 days 22 | with no activity. Remove stale label or comment or this PR will be closed in 10 days 23 | # Not stale if have this labels or part of milestone 24 | exempt-issue-labels: bug,wip,on-hold 25 | exempt-pr-labels: bug,wip,on-hold 26 | exempt-all-milestones: true 27 | # Close issue operations 28 | # Label will be automatically removed if the issues are no longer closed nor locked. 29 | days-before-close: 10 30 | delete-branch: true 31 | close-issue-message: This issue was automatically closed because of stale in 10 days 32 | close-pr-message: This PR was automatically closed because of stale in 10 days 33 | -------------------------------------------------------------------------------- /modules/pricing/local_dev.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | local_dev = { 3 | # debug_output = true 4 | # call_aws_pricing_api = true 5 | # run_aws_cli_commands = false 6 | 7 | # Regex to select subset of keys to work with: 8 | # process_keys_regex = "aws_db_instance" 9 | 10 | # content = jsondecode(data.local_file.local_plan.content) 11 | } 12 | } 13 | # 14 | ######## 15 | ## Run aws_cli response to check results. 16 | ## jq -r ".PriceList[0] | fromjson 17 | ## jq -r ".PriceList[0] | fromjson | .terms.OnDemand[].priceDimensions[].pricePerUnit.USD" 18 | ######## 19 | #resource "null_resource" "aws_cli" { 20 | # for_each = local.run_aws_cli_commands ? local.aws_cli_commands : {} 21 | # 22 | # triggers = { 23 | # trigger = timestamp() 24 | # } 25 | # 26 | # provisioner "local-exec" { 27 | # command = each.value 28 | # } 29 | #} 30 | # 31 | ############################ 32 | ## Terraform plan (as JSON) 33 | ############################ 34 | #data "local_file" "local_plan" { 35 | # filename = "../../examples/fixtures/all-resources/plan.json" 36 | # 37 | # # filename = "../../examples/fixtures/etc/plan-ebs-volume.json" # scalar 38 | # 39 | # # filename = "../../examples/fixtures/etc/plan-ebs.json" # list(map) => local.extracted_filters_in_instances_multiple 40 | # 41 | # 42 | # # filename = "../../examples/fixtures/etc/plan-instance-with-multiple-ebs.json" 43 | # # filename = "../../examples/fixtures/etc/ebs-terraform-state.json" 44 | # # filename = "../../examples/fixtures/etc/plan-lb-and-nat.json" 45 | # # filename = "../../examples/fixtures/etc/ec2.terraform.tfstate" 46 | # 47 | # # filename = "../../examples/fixtures/combinations/plan.json" 48 | # # filename = "../../examples/fixtures/combinations/terraform.tfstate" 49 | #} 50 | -------------------------------------------------------------------------------- /examples/pricing-terraform-state-and-plan/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "us-east-1" 3 | } 4 | 5 | module "pricing_state" { 6 | source = "../../modules/pricing" 7 | 8 | # call_aws_pricing_api = false 9 | content = jsondecode(file(data.terraform_remote_state.local_state.config.path)) 10 | } 11 | 12 | module "pricing_plan" { 13 | source = "../../modules/pricing" 14 | 15 | # debug_output = true 16 | # call_aws_pricing_api = false 17 | 18 | content = jsondecode(data.local_file.local_plan.content) 19 | 20 | resources = { 21 | "aws_instance.this#3" = { # 3 instances 22 | instanceType = "c5.xlarge" 23 | location = "eu-west-2" 24 | } 25 | "aws_instance.this2" = { 26 | instanceType = "c4.xlarge" 27 | location = "eu-west-1" 28 | } 29 | } 30 | } 31 | 32 | #################### 33 | # Calculation check 34 | # ec2-all-together.tfstate and ec2-all-together-plan.json should produce the same cost estimation 35 | # Disabled because I have not created plan and state for both set of "all-resources" 36 | #################### 37 | 38 | #data "local_file" "all_together_state" { 39 | # filename = "../fixtures/ec2.terraform.tfstate" 40 | #} 41 | # 42 | #module "pricing_all_together_state" { 43 | # source = "../../modules/pricing" 44 | # 45 | # content = jsondecode(data.local_file.all_together_state.content) 46 | #} 47 | 48 | ################## 49 | # Terraform state 50 | ################## 51 | data "terraform_remote_state" "local_state" { 52 | backend = "local" 53 | 54 | config = { 55 | path = "../fixtures/etc/ec2.terraform.tfstate" 56 | } 57 | } 58 | 59 | ########################### 60 | # Terraform plan (as JSON) 61 | ########################### 62 | data "local_file" "local_plan" { 63 | filename = "../fixtures/all-resources/plan.json" 64 | } 65 | -------------------------------------------------------------------------------- /examples/pricing-resources/README.md: -------------------------------------------------------------------------------- 1 | # Infrastructure costs for AWS resources (static) 2 | 3 | Configuration in this directory calculates AWS infrastructure costs for specified AWS resources. 4 | 5 | Note that AWS provider should always be set to `us-east-1` or `ap-south-1` region. 6 | 7 | # Usage 8 | 9 | To run this example you need to execute: 10 | 11 | ```bash 12 | $ terraform init 13 | $ terraform plan 14 | $ terraform apply 15 | ``` 16 | 17 | Run `terraform destroy` when you don't need these resources. 18 | 19 | 20 | ## Requirements 21 | 22 | | Name | Version | 23 | |------|---------| 24 | | [terraform](#requirement\_terraform) | >= 1.0 | 25 | | [aws](#requirement\_aws) | >= 4.0 | 26 | 27 | ## Providers 28 | 29 | No providers. 30 | 31 | ## Modules 32 | 33 | | Name | Source | Version | 34 | |------|--------|---------| 35 | | [pricing](#module\_pricing) | ../../modules/pricing | n/a | 36 | 37 | ## Resources 38 | 39 | No resources. 40 | 41 | ## Inputs 42 | 43 | No inputs. 44 | 45 | ## Outputs 46 | 47 | | Name | Description | 48 | |------|-------------| 49 | | [input\_resources](#output\_input\_resources) | Map of input resource filters (from plan/state or static) | 50 | | [pricing\_per\_resources](#output\_pricing\_per\_resources) | Map of resource pricing | 51 | | [pricing\_product\_filters](#output\_pricing\_product\_filters) | Map of pricing product filters (as they are submitted using data source `aws_pricing_product`) | 52 | | [resource\_quantity](#output\_resource\_quantity) | Map of resource quantity | 53 | | [resources](#output\_resources) | Map of provided resources with filters | 54 | | [total\_price\_per\_hour](#output\_total\_price\_per\_hour) | Total price for all resources | 55 | | [total\_price\_per\_month](#output\_total\_price\_per\_month) | Total price for all resources per month (730 hours) | 56 | 57 | -------------------------------------------------------------------------------- /examples/fixtures/combinations/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-west-1" 3 | } 4 | 5 | ################################################################## 6 | # Data sources to get VPC, subnet, security group and AMI details 7 | ################################################################## 8 | 9 | data "aws_vpc" "default" { 10 | default = true 11 | } 12 | 13 | data "aws_subnets" "all" { 14 | filter { 15 | name = "vpc-id" 16 | values = [data.aws_vpc.default.id] 17 | } 18 | } 19 | 20 | data "aws_ami" "amazon_linux" { 21 | most_recent = true 22 | owners = ["amazon"] 23 | 24 | filter { 25 | name = "name" 26 | values = ["amzn-ami-hvm-*-x86_64-gp2"] 27 | } 28 | 29 | filter { 30 | name = "owner-alias" 31 | values = ["amazon"] 32 | } 33 | } 34 | 35 | module "instance_count_2" { 36 | source = "terraform-aws-modules/ec2-instance/aws" 37 | version = "~> 3.0" 38 | 39 | name = "instance_count_" 40 | ami = data.aws_ami.amazon_linux.id 41 | instance_type = "t2.micro" 42 | subnet_id = element(data.aws_subnets.all.ids, 0) 43 | 44 | root_block_device = [ 45 | { 46 | volume_type = "gp2" 47 | volume_size = 10 48 | }, 49 | ] 50 | } 51 | 52 | module "module_count_2" { 53 | source = "terraform-aws-modules/ec2-instance/aws" 54 | version = "~> 3.0" 55 | 56 | name = "module_count_" 57 | ami = data.aws_ami.amazon_linux.id 58 | instance_type = "t2.nano" 59 | subnet_id = element(data.aws_subnets.all.ids, 0) 60 | 61 | } 62 | 63 | ######### 64 | 65 | resource "aws_instance" "small_no_count" { 66 | ami = data.aws_ami.amazon_linux.id 67 | instance_type = "t3.small" 68 | } 69 | 70 | resource "aws_instance" "nano_count_2" { 71 | count = 2 72 | 73 | ami = data.aws_ami.amazon_linux.id 74 | instance_type = "t3.nano" 75 | subnet_id = element(data.aws_subnets.all.ids, 0) 76 | } 77 | 78 | ########## 79 | 80 | resource "aws_instance" "small_for_each_fixed" { 81 | for_each = { key1 : "value1", key2 : "value2" } 82 | 83 | ami = data.aws_ami.amazon_linux.id 84 | instance_type = "t3.small" 85 | subnet_id = element(data.aws_subnets.all.ids, 0) 86 | 87 | tags = { Name : each.value } 88 | } 89 | -------------------------------------------------------------------------------- /modules/pricing/outputs.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | pricing_results = { for k, v in data.aws_pricing_product.this : k => jsondecode(v.result) } 3 | 4 | pricing_per_resources = { for k, v in local.pricing_results : k => tonumber(values(values(v.terms.OnDemand)[0].priceDimensions)[0].pricePerUnit.USD) } 5 | 6 | # Trying to calculate sum of an empty list 7 | total_price = try(sum([for k, v in local.pricing_per_resources : v * lookup(local.resource_quantity, k, 1)]), 0) 8 | } 9 | 10 | output "resources" { 11 | description = "Map of provided resources with filters" 12 | value = local.debug_output ? local.resources : {} 13 | } 14 | 15 | output "input_resources" { 16 | description = "Map of input resource filters (from plan/state or static)" 17 | value = local.debug_output ? local.input_resources : {} 18 | } 19 | 20 | output "pricing_product_filters" { 21 | description = "Map of pricing product filters (as they are submitted using data source `aws_pricing_product`)" 22 | value = local.debug_output ? local.pricing_product_filters : {} 23 | } 24 | 25 | output "resource_quantity" { 26 | description = "Map of resource quantity" 27 | value = local.resource_quantity 28 | } 29 | 30 | #output "aws_region_descriptions" { 31 | # description = "Map of supported AWS regions after conversion for pricing API" 32 | # value = local.debug_output ? local.all_aws_regions : {} 33 | #} 34 | 35 | output "pricing_per_resources" { 36 | description = "Map of resource pricing" 37 | value = local.pricing_per_resources 38 | } 39 | 40 | output "total_price_per_hour" { 41 | description = "Total price for all resources per hour" 42 | value = tonumber(format(format("%%.%df", var.hourly_price_precision), local.total_price)) 43 | } 44 | 45 | output "total_price_per_month" { 46 | description = "Total price for all resources per month (730 hours)" 47 | value = tonumber(format(format("%%.%df", var.monthly_price_precision), local.total_price * 730)) 48 | } 49 | 50 | ######## 51 | # Debug 52 | ######## 53 | output "aws_cli_commands" { 54 | description = "AWS CLI commands identical to AWS Pricing API calls. This should always return value (preferably one value). Adjust filters accordingly." 55 | value = local.debug_output ? local.aws_cli_commands : {} 56 | } 57 | -------------------------------------------------------------------------------- /modules/pricing/variables.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | debug_output = lookup(local.local_dev, "debug_output", var.debug_output) 3 | call_aws_pricing_api = lookup(local.local_dev, "call_aws_pricing_api", var.call_aws_pricing_api) 4 | # run_aws_cli_commands = lookup(local.local_dev, "run_aws_cli_commands", false) 5 | 6 | content = lookup(local.local_dev, "content", var.content) 7 | } 8 | 9 | # Inputs 10 | variable "resources" { 11 | description = "Map of all resources to calculate price for" 12 | type = any 13 | default = {} 14 | } 15 | 16 | variable "content" { 17 | description = "JSON object containing data of Terraform plan or state" 18 | type = any 19 | default = {} 20 | } 21 | 22 | # Debug 23 | variable "call_aws_pricing_api" { 24 | description = "Whether to call AWS Pricing API for real or just output filter (it is useful to disable this to see filters instead of calling API)" 25 | type = bool 26 | default = true 27 | } 28 | 29 | variable "debug_output" { 30 | description = "Whether to populate more output (useful for debug, but increase verbosity and size of tfstate)" 31 | type = bool 32 | default = false 33 | } 34 | 35 | # Settings 36 | variable "hourly_price_precision" { 37 | description = "Number of digits after comma in hourly price" 38 | type = number 39 | default = 10 40 | } 41 | 42 | variable "monthly_price_precision" { 43 | description = "Number of digits after comma in monthly price" 44 | type = number 45 | default = 2 46 | } 47 | 48 | variable "query_all_regions" { 49 | description = "If true the source will query all regions regardless of availability" 50 | type = bool 51 | default = true 52 | } 53 | 54 | # Defaults 55 | variable "aws_default_region" { 56 | description = "Default AWS region to use for resources (if not set) when asking AWS Pricing API" 57 | type = string 58 | default = "us-east-1" 59 | } 60 | 61 | variable "aws_default_ebs_volume_type" { 62 | description = "Default type of EBS volume to use for resources (if not set) when asking AWS Pricing API" 63 | type = string 64 | default = "gp2" 65 | } 66 | 67 | variable "aws_default_ebs_volume_size" { 68 | description = "Default size of EBS volume to use for resources (if not set) when asking AWS Pricing API" 69 | type = number 70 | default = 100 71 | } 72 | -------------------------------------------------------------------------------- /.github/workflows/pr-title.yml: -------------------------------------------------------------------------------- 1 | name: 'Validate PR title' 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | jobs: 11 | main: 12 | name: Validate PR title 13 | runs-on: ubuntu-latest 14 | steps: 15 | # Please look up the latest version from 16 | # https://github.com/amannn/action-semantic-pull-request/releases 17 | - uses: amannn/action-semantic-pull-request@v5.5.3 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | with: 21 | # Configure which types are allowed. 22 | # Default: https://github.com/commitizen/conventional-commit-types 23 | types: | 24 | fix 25 | feat 26 | docs 27 | ci 28 | chore 29 | # Configure that a scope must always be provided. 30 | requireScope: false 31 | # Configure additional validation for the subject based on a regex. 32 | # This example ensures the subject starts with an uppercase character. 33 | subjectPattern: ^[A-Z].+$ 34 | # If `subjectPattern` is configured, you can use this property to override 35 | # the default error message that is shown when the pattern doesn't match. 36 | # The variables `subject` and `title` can be used within the message. 37 | subjectPatternError: | 38 | The subject "{subject}" found in the pull request title "{title}" 39 | didn't match the configured pattern. Please ensure that the subject 40 | starts with an uppercase character. 41 | # For work-in-progress PRs you can typically use draft pull requests 42 | # from Github. However, private repositories on the free plan don't have 43 | # this option and therefore this action allows you to opt-in to using the 44 | # special "[WIP]" prefix to indicate this state. This will avoid the 45 | # validation of the PR title and the pull request checks remain pending. 46 | # Note that a second check will be reported if this is enabled. 47 | wip: true 48 | # When using "Squash and merge" on a PR with only one commit, GitHub 49 | # will suggest using that commit message instead of the PR title for the 50 | # merge commit, and it's easy to commit this by mistake. Enable this option 51 | # to also validate the commit message for one commit PRs. 52 | validateSingleCommit: false 53 | -------------------------------------------------------------------------------- /examples/cost-modules-tf/README.md: -------------------------------------------------------------------------------- 1 | # cost.modules.tf example 2 | 3 | Configuration in this directory sends `terraform.tfstate` file content to `cost.modules.tf` to calculate AWS infrastructure costs. 4 | 5 | # Usage 6 | 7 | To run this example you need to execute: 8 | 9 | ```bash 10 | $ terraform init 11 | $ terraform plan 12 | $ terraform apply 13 | ``` 14 | 15 | Run `terraform destroy` when you don't need these resources. 16 | 17 | 18 | ## Requirements 19 | 20 | | Name | Version | 21 | |------|---------| 22 | | [terraform](#requirement\_terraform) | >= 1.0 | 23 | | [aws](#requirement\_aws) | >= 4.0 | 24 | | [local](#requirement\_local) | >= 1.0 | 25 | 26 | ## Providers 27 | 28 | | Name | Version | 29 | |------|---------| 30 | | [local](#provider\_local) | >= 1.0 | 31 | | [terraform](#provider\_terraform) | n/a | 32 | 33 | ## Modules 34 | 35 | | Name | Source | Version | 36 | |------|--------|---------| 37 | | [plan](#module\_plan) | ../../modules/cost.modules.tf | n/a | 38 | | [tfstate](#module\_tfstate) | ../../modules/cost.modules.tf | n/a | 39 | | [tfstate\_with\_terraform\_remote\_state](#module\_tfstate\_with\_terraform\_remote\_state) | ../../modules/cost.modules.tf | n/a | 40 | 41 | ## Resources 42 | 43 | | Name | Type | 44 | |------|------| 45 | | [local_file.plan](https://registry.terraform.io/providers/hashicorp/local/latest/docs/data-sources/file) | data source | 46 | | [local_file.tfstate](https://registry.terraform.io/providers/hashicorp/local/latest/docs/data-sources/file) | data source | 47 | | [terraform_remote_state.local_state](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/data-sources/remote_state) | data source | 48 | 49 | ## Inputs 50 | 51 | No inputs. 52 | 53 | ## Outputs 54 | 55 | | Name | Description | 56 | |------|-------------| 57 | | [price\_plan](#output\_price\_plan) | Costs for local file - plan | 58 | | [price\_tfstate](#output\_price\_tfstate) | Costs for local file - state | 59 | | [price\_tfstate\_with\_terraform\_remote\_state](#output\_price\_tfstate\_with\_terraform\_remote\_state) | Costs for remote state | 60 | 61 | -------------------------------------------------------------------------------- /modules/pricing/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | # Merge inputs: 3 | # 1. Extracted resource filters from state or plan 4 | # 2. Static resources 5 | input_resources = merge(local.instance_final_filters, var.resources) 6 | 7 | # resources_combined = resources + resource_defaults 8 | resources_combined = { 9 | for k, v in local.input_resources : 10 | k => ( 11 | merge(lookup(local.resource_defaults, split(".", k)[0], {}), v) 12 | ) 13 | } 14 | 15 | # apply/reformat location values in resources_combined 16 | resources_location = { 17 | for k, v in local.resources_combined : k => { 18 | for k1, v1 in v : 19 | k1 => ( 20 | matchkeys(keys(local.all_aws_regions), values(local.all_aws_regions), [v1])[0] 21 | ) if k1 == "location" 22 | } 23 | } 24 | 25 | # resources = resources_combined + resources_location 26 | resources = { 27 | for k, v in local.resources_combined : 28 | k => ( 29 | merge(local.resources_combined[k], local.resources_location[k]) 30 | ) 31 | } 32 | 33 | # There are 2 types of quantities supported: 34 | # 1. Implicit (eg. count of similar resources) - "aws_instance.this#5" means 5 instances 35 | # 2. Explicit (eg, EBS volume size has pricing as GB/Month so converting to Gb/Hour): 36 | # { "aws_ebs_volume.v1.aws_ebs_volume.v1[0]" = {"_quantity" = "10", "_divisor" = 730, "volumeApiName" = "io2"} } 37 | resource_quantity = { 38 | for k, v in local.resources : 39 | k => ( 40 | tonumber( 41 | try(split("#", k)[1], 1) * try( 42 | format("%.10f", local.resources[k]["_quantity"] / lookup(local.resources[k], "_divisor", 1) 43 | ), 1) 44 | ) 45 | ) 46 | } 47 | 48 | # Final AWS Pricing Product filters as they will be send to API and/or output for debug 49 | pricing_product_filters = { 50 | for k, v in local.resources : 51 | k => { 52 | service_code : local.resources_service_code[split(".", k)[0]] 53 | filters : { 54 | for f_k, f_v in v : 55 | f_k => f_v 56 | if !contains(["_quantity", "_divisor"], f_k) 57 | } 58 | } 59 | } 60 | } 61 | 62 | # @todo: Change a key to be composite of all keys, so that during reapply everything resets 63 | data "aws_pricing_product" "this" { 64 | for_each = local.call_aws_pricing_api ? local.pricing_product_filters : {} 65 | 66 | service_code = each.value.service_code 67 | 68 | dynamic "filters" { 69 | for_each = each.value.filters 70 | 71 | content { 72 | field = filters.key 73 | value = filters.value 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /modules/cost.modules.tf/README.md: -------------------------------------------------------------------------------- 1 | # Using cost.modules.tf 2 | 3 | This module gets cost estimation from `cost.modules.tf` service based on the provided content of Terraform state or plan file as json. 4 | 5 | `cost.modules.tf` is entirely free cost estimation service, which is part of [modules.tf](https://modules.tf) that is currently in active development. 6 | 7 | See [repository terraform-cost-estimation](https://github.com/antonbabenko/terraform-cost-estimation) and [terraform-cost-estimation.com](https://www.terraform-cost-estimation.com/) for more information. 8 | 9 | 10 | ## Requirements 11 | 12 | | Name | Version | 13 | |------|---------| 14 | | [terraform](#requirement\_terraform) | >= 1.0 | 15 | | [local](#requirement\_local) | >= 1.0 | 16 | | [null](#requirement\_null) | >= 2.0 | 17 | 18 | ## Providers 19 | 20 | | Name | Version | 21 | |------|---------| 22 | | [local](#provider\_local) | >= 1.0 | 23 | | [null](#provider\_null) | >= 2.0 | 24 | 25 | ## Modules 26 | 27 | No modules. 28 | 29 | ## Resources 30 | 31 | | Name | Type | 32 | |------|------| 33 | | [local_file.json_input](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource | 34 | | [null_resource.curl](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | 35 | | [null_data_source.costs](https://registry.terraform.io/providers/hashicorp/null/latest/docs/data-sources/data_source) | data source | 36 | 37 | ## Inputs 38 | 39 | | Name | Description | Type | Default | Required | 40 | |------|-------------|------|---------|:--------:| 41 | | [content](#input\_content) | Content of tfstate or plan file as json | `string` | n/a | yes | 42 | | [enabled](#input\_enabled) | Whether to enable this module and call cost.modules.tf | `bool` | `true` | no | 43 | | [filename\_hash](#input\_filename\_hash) | Extra hash to add to created filenames | `string` | `""` | no | 44 | | [tmp\_dir](#input\_tmp\_dir) | Name of local temp directory to create files in | `string` | `"tmp"` | no | 45 | 46 | ## Outputs 47 | 48 | | Name | Description | 49 | |------|-------------| 50 | | [costs](#output\_costs) | Total costs | 51 | | [hourly](#output\_hourly) | Hourly costs | 52 | | [monthly](#output\_monthly) | Monthly costs | 53 | 54 | -------------------------------------------------------------------------------- /examples/pricing-dev/README.md: -------------------------------------------------------------------------------- 1 | # Pricing playground to verify multiple fixtures easily 2 | 3 | # Usage 4 | 5 | To run this example you need to execute: 6 | 7 | ```bash 8 | $ terraform init 9 | $ terraform plan 10 | $ terraform apply 11 | ``` 12 | 13 | Run `terraform destroy` when you don't need these resources. 14 | 15 | 16 | ## Requirements 17 | 18 | | Name | Version | 19 | |------|---------| 20 | | [terraform](#requirement\_terraform) | >= 1.0 | 21 | | [aws](#requirement\_aws) | >= 4.0 | 22 | | [local](#requirement\_local) | >= 1.0 | 23 | 24 | ## Providers 25 | 26 | | Name | Version | 27 | |------|---------| 28 | | [local](#provider\_local) | >= 1.0 | 29 | 30 | ## Modules 31 | 32 | | Name | Source | Version | 33 | |------|--------|---------| 34 | | [pricing](#module\_pricing) | ../../modules/pricing | n/a | 35 | 36 | ## Resources 37 | 38 | | Name | Type | 39 | |------|------| 40 | | [local_file.local_plan](https://registry.terraform.io/providers/hashicorp/local/latest/docs/data-sources/file) | data source | 41 | 42 | ## Inputs 43 | 44 | No inputs. 45 | 46 | ## Outputs 47 | 48 | | Name | Description | 49 | |------|-------------| 50 | | [aws\_cli\_commands](#output\_aws\_cli\_commands) | List of AWS CLI commands with Pricing filters | 51 | | [pricing\_input\_resources](#output\_pricing\_input\_resources) | Map of input resource filters (from plan/state or static) | 52 | | [pricing\_pricing\_per\_resources](#output\_pricing\_pricing\_per\_resources) | Map of resource pricing | 53 | | [pricing\_pricing\_product\_filters](#output\_pricing\_pricing\_product\_filters) | Map of pricing product filters (as they are submitted using data source `aws_pricing_product`) | 54 | | [pricing\_resource\_quantity](#output\_pricing\_resource\_quantity) | Map of resource quantity | 55 | | [pricing\_resources](#output\_pricing\_resources) | Map of provided resources with filters | 56 | | [pricing\_total\_price\_per\_hour](#output\_pricing\_total\_price\_per\_hour) | Total price for all resources | 57 | | [pricing\_total\_price\_per\_month](#output\_pricing\_total\_price\_per\_month) | Total price for all resources per month (730 hours) | 58 | 59 | -------------------------------------------------------------------------------- /examples/complete/README.md: -------------------------------------------------------------------------------- 1 | # Infrastructure costs from various sources combined 2 | 3 | Configuration in this directory calculates AWS infrastructure costs for specified Terraform plan (as JSON file) with extra defined resources. 4 | 5 | Note that AWS provider should always be set to `us-east-1` or `ap-south-1` region. 6 | 7 | # Usage 8 | 9 | To run this example you need to execute: 10 | 11 | ```bash 12 | $ terraform init 13 | $ terraform plan 14 | $ terraform apply 15 | ``` 16 | 17 | Run `terraform destroy` when you don't need these resources. 18 | 19 | 20 | ## Requirements 21 | 22 | | Name | Version | 23 | |------|---------| 24 | | [terraform](#requirement\_terraform) | >= 1.0 | 25 | | [aws](#requirement\_aws) | >= 4.0 | 26 | | [local](#requirement\_local) | >= 1.0 | 27 | 28 | ## Providers 29 | 30 | | Name | Version | 31 | |------|---------| 32 | | [local](#provider\_local) | >= 1.0 | 33 | 34 | ## Modules 35 | 36 | | Name | Source | Version | 37 | |------|--------|---------| 38 | | [pricing](#module\_pricing) | ../../modules/pricing | n/a | 39 | 40 | ## Resources 41 | 42 | | Name | Type | 43 | |------|------| 44 | | [local_file.local_plan](https://registry.terraform.io/providers/hashicorp/local/latest/docs/data-sources/file) | data source | 45 | 46 | ## Inputs 47 | 48 | No inputs. 49 | 50 | ## Outputs 51 | 52 | | Name | Description | 53 | |------|-------------| 54 | | [pricing\_input\_resources](#output\_pricing\_input\_resources) | Map of input resource filters (from plan/state or static) | 55 | | [pricing\_pricing\_per\_resources](#output\_pricing\_pricing\_per\_resources) | Map of resource pricing | 56 | | [pricing\_pricing\_product\_filters](#output\_pricing\_pricing\_product\_filters) | Map of pricing product filters (as they are submitted using data source `aws_pricing_product`) | 57 | | [pricing\_resource\_quantity](#output\_pricing\_resource\_quantity) | Map of resource quantity | 58 | | [pricing\_resources](#output\_pricing\_resources) | Map of provided resources with filters | 59 | | [pricing\_total\_price\_per\_hour](#output\_pricing\_total\_price\_per\_hour) | Total price for all resources | 60 | | [pricing\_total\_price\_per\_month](#output\_pricing\_total\_price\_per\_month) | Total price for all resources per month (730 hours) | 61 | 62 | -------------------------------------------------------------------------------- /examples/pricing-terraform-state-and-plan/outputs.tf: -------------------------------------------------------------------------------- 1 | #output "calculation_check" { 2 | # description = "Costs for both plan and state should match" 3 | # value = module.pricing_plan.total_price_per_month == module.pricing_all_together_state.total_price_per_month 4 | #} 5 | 6 | ################# 7 | # Terraform plan 8 | ################# 9 | output "pricing_plan_resources" { 10 | description = "Map of provided resources with filters" 11 | value = module.pricing_plan.resources 12 | } 13 | 14 | output "pricing_plan_input_resources" { 15 | description = "Map of input resource filters (from plan/state or static)" 16 | value = module.pricing_plan.input_resources 17 | } 18 | 19 | output "pricing_plan_pricing_product_filters" { 20 | description = "Map of pricing product filters (as they are submitted using data source `aws_pricing_product`)" 21 | value = module.pricing_plan.pricing_product_filters 22 | } 23 | 24 | output "pricing_plan_resource_quantity" { 25 | description = "Map of resource quantity" 26 | value = module.pricing_plan.resource_quantity 27 | } 28 | 29 | output "pricing_plan_pricing_per_resources" { 30 | description = "Map of resource pricing" 31 | value = module.pricing_plan.pricing_per_resources 32 | } 33 | 34 | output "pricing_plan_total_price_per_hour" { 35 | description = "Total price for all resources" 36 | value = module.pricing_plan.total_price_per_hour 37 | } 38 | 39 | output "pricing_plan_total_price_per_month" { 40 | description = "Total price for all resources per month (730 hours)" 41 | value = module.pricing_plan.total_price_per_month 42 | } 43 | 44 | ################## 45 | # Terraform state 46 | ################## 47 | output "pricing_state_resources" { 48 | description = "Map of provided resources with filters" 49 | value = module.pricing_state.resources 50 | } 51 | 52 | output "pricing_state_input_resources" { 53 | description = "Map of input resource filters (from plan/state or static)" 54 | value = module.pricing_state.input_resources 55 | } 56 | 57 | output "pricing_state_pricing_product_filters" { 58 | description = "Map of pricing product filters (as they are submitted using data source `aws_pricing_product`)" 59 | value = module.pricing_state.pricing_product_filters 60 | } 61 | 62 | output "pricing_state_resource_quantity" { 63 | description = "Map of resource quantity" 64 | value = module.pricing_state.resource_quantity 65 | } 66 | 67 | output "pricing_state_pricing_per_resources" { 68 | description = "Map of resource pricing" 69 | value = module.pricing_state.pricing_per_resources 70 | } 71 | 72 | output "pricing_state_total_price_per_hour" { 73 | description = "Total price for all resources" 74 | value = module.pricing_state.total_price_per_hour 75 | } 76 | 77 | output "pricing_state_total_price_per_month" { 78 | description = "Total price for all resources per month (730 hours)" 79 | value = module.pricing_state.total_price_per_month 80 | } 81 | -------------------------------------------------------------------------------- /examples/fixtures/etc/ebs-terraform-state.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "terraform_version": "0.12.24", 4 | "serial": 6, 5 | "lineage": "13540a77-ced5-199b-9c94-227a8af363ac", 6 | "outputs": {}, 7 | "resources": [ 8 | { 9 | "mode": "managed", 10 | "type": "aws_ebs_snapshot", 11 | "name": "standard", 12 | "provider": "provider.aws", 13 | "instances": [ 14 | { 15 | "schema_version": 0, 16 | "attributes": { 17 | "data_encryption_key_id": "", 18 | "description": "", 19 | "encrypted": false, 20 | "id": "snap-004617595d68d690d", 21 | "kms_key_id": "", 22 | "owner_alias": "", 23 | "owner_id": "052235879155", 24 | "tags": {}, 25 | "timeouts": null, 26 | "volume_id": "vol-08c7383eaa3f690f5", 27 | "volume_size": 7 28 | }, 29 | "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6NjAwMDAwMDAwMDAwfX0=", 30 | "dependencies": [ 31 | "aws_ebs_volume.volume_standard" 32 | ] 33 | } 34 | ] 35 | }, 36 | { 37 | "mode": "managed", 38 | "type": "aws_ebs_snapshot_copy", 39 | "name": "copied", 40 | "provider": "provider.aws", 41 | "instances": [ 42 | { 43 | "schema_version": 0, 44 | "attributes": { 45 | "data_encryption_key_id": "", 46 | "description": "", 47 | "encrypted": false, 48 | "id": "snap-0526e7ccbf359032c", 49 | "kms_key_id": "", 50 | "owner_alias": "", 51 | "owner_id": "052235879155", 52 | "source_region": "eu-west-1", 53 | "source_snapshot_id": "snap-004617595d68d690d", 54 | "tags": {}, 55 | "volume_id": "vol-ffffffff", 56 | "volume_size": 7 57 | }, 58 | "private": "bnVsbA==", 59 | "dependencies": [ 60 | "aws_ebs_snapshot.standard", 61 | "aws_ebs_volume.volume_standard" 62 | ] 63 | } 64 | ] 65 | }, 66 | { 67 | "mode": "managed", 68 | "type": "aws_ebs_volume", 69 | "name": "volume_io1", 70 | "provider": "provider.aws", 71 | "instances": [ 72 | { 73 | "schema_version": 0, 74 | "attributes": { 75 | "arn": "arn:aws:ec2:eu-west-1:052235879155:volume/vol-02a74d481bdd8635c", 76 | "availability_zone": "eu-west-1a", 77 | "encrypted": false, 78 | "id": "vol-02a74d481bdd8635c", 79 | "iops": 300, 80 | "kms_key_id": "", 81 | "outpost_arn": "", 82 | "size": 8, 83 | "snapshot_id": "", 84 | "tags": null, 85 | "type": "io1" 86 | }, 87 | "private": "bnVsbA==" 88 | } 89 | ] 90 | }, 91 | { 92 | "mode": "managed", 93 | "type": "aws_ebs_volume", 94 | "name": "volume_standard", 95 | "provider": "provider.aws", 96 | "instances": [ 97 | { 98 | "schema_version": 0, 99 | "attributes": { 100 | "arn": "arn:aws:ec2:eu-west-1:052235879155:volume/vol-08c7383eaa3f690f5", 101 | "availability_zone": "eu-west-1a", 102 | "encrypted": false, 103 | "id": "vol-08c7383eaa3f690f5", 104 | "iops": 0, 105 | "kms_key_id": "", 106 | "outpost_arn": "", 107 | "size": 7, 108 | "snapshot_id": "", 109 | "tags": {}, 110 | "type": "standard" 111 | }, 112 | "private": "bnVsbA==" 113 | } 114 | ] 115 | } 116 | ] 117 | } 118 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: Pre-Commit 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | - master 8 | 9 | env: 10 | TERRAFORM_DOCS_VERSION: v0.19.0 11 | TFLINT_VERSION: v0.53.0 12 | 13 | jobs: 14 | collectInputs: 15 | name: Collect workflow inputs 16 | runs-on: ubuntu-latest 17 | outputs: 18 | directories: ${{ steps.dirs.outputs.directories }} 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Get root directories 24 | id: dirs 25 | uses: clowdhaus/terraform-composite-actions/directories@v1.9.0 26 | 27 | preCommitMinVersions: 28 | name: Min TF pre-commit 29 | needs: collectInputs 30 | runs-on: ubuntu-latest 31 | strategy: 32 | matrix: 33 | directory: ${{ fromJson(needs.collectInputs.outputs.directories) }} 34 | steps: 35 | # https://github.com/orgs/community/discussions/25678#discussioncomment-5242449 36 | - name: Delete huge unnecessary tools folder 37 | run: | 38 | rm -rf /opt/hostedtoolcache/CodeQL 39 | rm -rf /opt/hostedtoolcache/Java_Temurin-Hotspot_jdk 40 | rm -rf /opt/hostedtoolcache/Ruby 41 | rm -rf /opt/hostedtoolcache/go 42 | 43 | - name: Checkout 44 | uses: actions/checkout@v4 45 | 46 | - name: Terraform min/max versions 47 | id: minMax 48 | uses: clowdhaus/terraform-min-max@v1.3.1 49 | with: 50 | directory: ${{ matrix.directory }} 51 | 52 | - name: Pre-commit Terraform ${{ steps.minMax.outputs.minVersion }} 53 | # Run only validate pre-commit check on min version supported 54 | if: ${{ matrix.directory != '.' }} 55 | uses: clowdhaus/terraform-composite-actions/pre-commit@v1.11.1 56 | with: 57 | terraform-version: ${{ steps.minMax.outputs.minVersion }} 58 | tflint-version: ${{ env.TFLINT_VERSION }} 59 | args: 'terraform_validate --color=always --show-diff-on-failure --files ${{ matrix.directory }}/*' 60 | 61 | - name: Pre-commit Terraform ${{ steps.minMax.outputs.minVersion }} 62 | # Run only validate pre-commit check on min version supported 63 | if: ${{ matrix.directory == '.' }} 64 | uses: clowdhaus/terraform-composite-actions/pre-commit@v1.11.1 65 | with: 66 | terraform-version: ${{ steps.minMax.outputs.minVersion }} 67 | tflint-version: ${{ env.TFLINT_VERSION }} 68 | args: 'terraform_validate --color=always --show-diff-on-failure --files $(ls *.tf)' 69 | 70 | preCommitMaxVersion: 71 | name: Max TF pre-commit 72 | runs-on: ubuntu-latest 73 | needs: collectInputs 74 | steps: 75 | # https://github.com/orgs/community/discussions/25678#discussioncomment-5242449 76 | - name: Delete huge unnecessary tools folder 77 | run: | 78 | rm -rf /opt/hostedtoolcache/CodeQL 79 | rm -rf /opt/hostedtoolcache/Java_Temurin-Hotspot_jdk 80 | rm -rf /opt/hostedtoolcache/Ruby 81 | rm -rf /opt/hostedtoolcache/go 82 | 83 | - name: Checkout 84 | uses: actions/checkout@v4 85 | with: 86 | ref: ${{ github.event.pull_request.head.ref }} 87 | repository: ${{github.event.pull_request.head.repo.full_name}} 88 | 89 | - name: Terraform min/max versions 90 | id: minMax 91 | uses: clowdhaus/terraform-min-max@v1.3.1 92 | 93 | - name: Pre-commit Terraform ${{ steps.minMax.outputs.maxVersion }} 94 | uses: clowdhaus/terraform-composite-actions/pre-commit@v1.11.1 95 | with: 96 | terraform-version: ${{ steps.minMax.outputs.maxVersion }} 97 | tflint-version: ${{ env.TFLINT_VERSION }} 98 | terraform-docs-version: ${{ env.TERRAFORM_DOCS_VERSION }} 99 | install-hcledit: true 100 | -------------------------------------------------------------------------------- /examples/pricing-terraform-state-and-plan/README.md: -------------------------------------------------------------------------------- 1 | # Infrastructure costs from Terraform state and plan (as JSON) 2 | 3 | Configuration in this directory calculates AWS infrastructure costs for specified Terraform state and plan (as JSON file). 4 | 5 | Note that AWS provider should always be set to `us-east-1` or `ap-south-1` region. 6 | 7 | # Usage 8 | 9 | To run this example you need to execute: 10 | 11 | ```bash 12 | $ terraform init 13 | $ terraform plan 14 | $ terraform apply 15 | ``` 16 | 17 | Run `terraform destroy` when you don't need these resources. 18 | 19 | 20 | ## Requirements 21 | 22 | | Name | Version | 23 | |------|---------| 24 | | [terraform](#requirement\_terraform) | >= 1.0 | 25 | | [aws](#requirement\_aws) | >= 4.0 | 26 | | [local](#requirement\_local) | >= 1.0 | 27 | 28 | ## Providers 29 | 30 | | Name | Version | 31 | |------|---------| 32 | | [local](#provider\_local) | >= 1.0 | 33 | | [terraform](#provider\_terraform) | n/a | 34 | 35 | ## Modules 36 | 37 | | Name | Source | Version | 38 | |------|--------|---------| 39 | | [pricing\_plan](#module\_pricing\_plan) | ../../modules/pricing | n/a | 40 | | [pricing\_state](#module\_pricing\_state) | ../../modules/pricing | n/a | 41 | 42 | ## Resources 43 | 44 | | Name | Type | 45 | |------|------| 46 | | [local_file.local_plan](https://registry.terraform.io/providers/hashicorp/local/latest/docs/data-sources/file) | data source | 47 | | [terraform_remote_state.local_state](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/data-sources/remote_state) | data source | 48 | 49 | ## Inputs 50 | 51 | No inputs. 52 | 53 | ## Outputs 54 | 55 | | Name | Description | 56 | |------|-------------| 57 | | [pricing\_plan\_input\_resources](#output\_pricing\_plan\_input\_resources) | Map of input resource filters (from plan/state or static) | 58 | | [pricing\_plan\_pricing\_per\_resources](#output\_pricing\_plan\_pricing\_per\_resources) | Map of resource pricing | 59 | | [pricing\_plan\_pricing\_product\_filters](#output\_pricing\_plan\_pricing\_product\_filters) | Map of pricing product filters (as they are submitted using data source `aws_pricing_product`) | 60 | | [pricing\_plan\_resource\_quantity](#output\_pricing\_plan\_resource\_quantity) | Map of resource quantity | 61 | | [pricing\_plan\_resources](#output\_pricing\_plan\_resources) | Map of provided resources with filters | 62 | | [pricing\_plan\_total\_price\_per\_hour](#output\_pricing\_plan\_total\_price\_per\_hour) | Total price for all resources | 63 | | [pricing\_plan\_total\_price\_per\_month](#output\_pricing\_plan\_total\_price\_per\_month) | Total price for all resources per month (730 hours) | 64 | | [pricing\_state\_input\_resources](#output\_pricing\_state\_input\_resources) | Map of input resource filters (from plan/state or static) | 65 | | [pricing\_state\_pricing\_per\_resources](#output\_pricing\_state\_pricing\_per\_resources) | Map of resource pricing | 66 | | [pricing\_state\_pricing\_product\_filters](#output\_pricing\_state\_pricing\_product\_filters) | Map of pricing product filters (as they are submitted using data source `aws_pricing_product`) | 67 | | [pricing\_state\_resource\_quantity](#output\_pricing\_state\_resource\_quantity) | Map of resource quantity | 68 | | [pricing\_state\_resources](#output\_pricing\_state\_resources) | Map of provided resources with filters | 69 | | [pricing\_state\_total\_price\_per\_hour](#output\_pricing\_state\_total\_price\_per\_hour) | Total price for all resources | 70 | | [pricing\_state\_total\_price\_per\_month](#output\_pricing\_state\_total\_price\_per\_month) | Total price for all resources per month (730 hours) | 71 | 72 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [2.0.3](https://github.com/terraform-aws-modules/terraform-aws-pricing/compare/v2.0.2...v2.0.3) (2024-03-07) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * Update CI workflow versions to remove deprecated runtime warnings ([#23](https://github.com/terraform-aws-modules/terraform-aws-pricing/issues/23)) ([179eccb](https://github.com/terraform-aws-modules/terraform-aws-pricing/commit/179eccb65e4fabf3106465035faabf4feb1feb3b)) 11 | 12 | ### [2.0.2](https://github.com/terraform-aws-modules/terraform-aws-pricing/compare/v2.0.1...v2.0.2) (2023-01-24) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * Updated GH Action version of terraform-composite-actions/directories ([#16](https://github.com/terraform-aws-modules/terraform-aws-pricing/issues/16)) ([61212bc](https://github.com/terraform-aws-modules/terraform-aws-pricing/commit/61212bcbf005119c7d189b6e47e32470a1985c21)) 18 | 19 | ### [2.0.1](https://github.com/terraform-aws-modules/terraform-aws-pricing/compare/v2.0.0...v2.0.1) (2022-11-15) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * Support for new AWS region ([#15](https://github.com/terraform-aws-modules/terraform-aws-pricing/issues/15)) ([b1d5988](https://github.com/terraform-aws-modules/terraform-aws-pricing/commit/b1d598882f64efb1aa1e4c43899a58e8bfc4adda)) 25 | 26 | ## [2.0.0](https://github.com/terraform-aws-modules/terraform-aws-pricing/compare/v1.4.0...v2.0.0) (2022-11-12) 27 | 28 | 29 | ### ⚠ BREAKING CHANGES 30 | 31 | * Update supported Terraform min version to v1.0+ and AWS provider to v4.0+ (#13) 32 | 33 | ### Features 34 | 35 | * Update supported Terraform min version to v1.0+ and AWS provider to v4.0+ ([#13](https://github.com/terraform-aws-modules/terraform-aws-pricing/issues/13)) ([904bebd](https://github.com/terraform-aws-modules/terraform-aws-pricing/commit/904bebd8c6483f5ebe46c5e1c085ca2952186223)) 36 | 37 | 38 | ### Bug Fixes 39 | 40 | * README example link ([#12](https://github.com/terraform-aws-modules/terraform-aws-pricing/issues/12)) ([39b47bf](https://github.com/terraform-aws-modules/terraform-aws-pricing/commit/39b47bf61973f6dd5dddc06e629e1d1c8d22dd39)) 41 | 42 | ## [1.4.0](https://github.com/terraform-aws-modules/terraform-aws-pricing/compare/v1.3.1...v1.4.0) (2022-09-13) 43 | 44 | 45 | ### Features 46 | 47 | * Added support for me-central-1 region ([#11](https://github.com/terraform-aws-modules/terraform-aws-pricing/issues/11)) ([cd4b957](https://github.com/terraform-aws-modules/terraform-aws-pricing/commit/cd4b957f9c8103e8c5f88b21e034ae64f36bee04)) 48 | 49 | ### [1.3.1](https://github.com/terraform-aws-modules/terraform-aws-pricing/compare/v1.3.0...v1.3.1) (2022-05-05) 50 | 51 | 52 | ### Bug Fixes 53 | 54 | * Corrects errors when plan contains no pricing changes ([#8](https://github.com/terraform-aws-modules/terraform-aws-pricing/issues/8)) ([8a062b9](https://github.com/terraform-aws-modules/terraform-aws-pricing/commit/8a062b92b966196f93ca505113470dcc5d6f4568)) 55 | 56 | # [1.3.0](https://github.com/terraform-aws-modules/terraform-aws-pricing/compare/v1.2.0...v1.3.0) (2021-12-17) 57 | 58 | 59 | ### Bug Fixes 60 | 61 | * Skip region ap-northeast-3 ([2691463](https://github.com/terraform-aws-modules/terraform-aws-pricing/commit/269146300413a75086279f85b4cd76ed2e12e9d4)) 62 | * update CI/CD process to enable auto-release workflow ([#4](https://github.com/terraform-aws-modules/terraform-aws-pricing/issues/4)) ([a80f318](https://github.com/terraform-aws-modules/terraform-aws-pricing/commit/a80f318e303cf11d88402a2cbb9a998fc4d247c7)) 63 | 64 | 65 | ### Features 66 | 67 | * Added GH Actions for sem release ([f406bbc](https://github.com/terraform-aws-modules/terraform-aws-pricing/commit/f406bbcd4d69243152f97823259c1f0e7af86abc)) 68 | * Added support for ap-southeast-3 region ([#6](https://github.com/terraform-aws-modules/terraform-aws-pricing/issues/6)) ([3041d75](https://github.com/terraform-aws-modules/terraform-aws-pricing/commit/3041d75c517afbd2f3069320048e593f0d480c85)) 69 | 70 | 71 | ## [v1.2.0] - 2021-05-25 72 | 73 | - chore: Remove check boxes that don't render properly in module doc ([#2](https://github.com/terraform-aws-modules/terraform-aws-pricing/issues/2)) 74 | 75 | 76 | 77 | ## [v1.1.0] - 2021-01-25 78 | 79 | - Fixed remaining examples 80 | 81 | 82 | 83 | ## v1.0.0 - 2021-01-25 84 | 85 | - feat: Added many filters and updates ([#1](https://github.com/terraform-aws-modules/terraform-aws-pricing/issues/1)) 86 | - Added most of the code 87 | 88 | 89 | [Unreleased]: https://github.com/terraform-aws-modules/terraform-aws-pricing/compare/v1.2.0...HEAD 90 | [v1.2.0]: https://github.com/terraform-aws-modules/terraform-aws-pricing/compare/v1.1.0...v1.2.0 91 | [v1.1.0]: https://github.com/terraform-aws-modules/terraform-aws-pricing/compare/v1.0.0...v1.1.0 92 | -------------------------------------------------------------------------------- /modules/pricing/README.md: -------------------------------------------------------------------------------- 1 | # AWS Pricing module 2 | 3 | This module gets cost estimation for AWS infrastructure by calling AWS Pricing API and aggregating results. 4 | 5 | ## What is supported as input? 6 | 7 | `var.content`: 8 | 1. Content of Terraform state file (as JSON) 9 | 2. Content of Terraform plan file (as JSON) 10 | 11 | `var.resources`: 12 | 1. Map of resources with pricing filters 13 | 14 | ## How it works? 15 | 16 | `state_and_plan_content_from_json.tf` - Content of state and plan file is normalized to the canonical structure which is compatible to the one defined as `var.resources`. 17 | 18 | ### Approximate order of execution: 19 | 20 | 1. `filters.tf` + `regions.tf` + `variables.tf` 21 | 2. `state_and_plan_content_from_json.tf` 22 | 3. `main.tf` 23 | 4. `outputs.tf` 24 | 25 | ## How to extend? How to contribute? 26 | 27 | `filters.tf` contains all AWS Pricing filters for supported resources. It is the main place to 28 | add support for new types of resources. 29 | 30 | `regions.tf` contains logic for expanding AWS regions map which is expected by Pricing API. 31 | 32 | 33 | ## Requirements 34 | 35 | | Name | Version | 36 | |------|---------| 37 | | [terraform](#requirement\_terraform) | >= 1.0 | 38 | | [aws](#requirement\_aws) | >= 4.0 | 39 | 40 | ## Providers 41 | 42 | | Name | Version | 43 | |------|---------| 44 | | [aws](#provider\_aws) | >= 4.0 | 45 | 46 | ## Modules 47 | 48 | No modules. 49 | 50 | ## Resources 51 | 52 | | Name | Type | 53 | |------|------| 54 | | [aws_pricing_product.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/pricing_product) | data source | 55 | | [aws_region.one](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | 56 | | [aws_regions.all](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/regions) | data source | 57 | 58 | ## Inputs 59 | 60 | | Name | Description | Type | Default | Required | 61 | |------|-------------|------|---------|:--------:| 62 | | [aws\_default\_ebs\_volume\_size](#input\_aws\_default\_ebs\_volume\_size) | Default size of EBS volume to use for resources (if not set) when asking AWS Pricing API | `number` | `100` | no | 63 | | [aws\_default\_ebs\_volume\_type](#input\_aws\_default\_ebs\_volume\_type) | Default type of EBS volume to use for resources (if not set) when asking AWS Pricing API | `string` | `"gp2"` | no | 64 | | [aws\_default\_region](#input\_aws\_default\_region) | Default AWS region to use for resources (if not set) when asking AWS Pricing API | `string` | `"us-east-1"` | no | 65 | | [call\_aws\_pricing\_api](#input\_call\_aws\_pricing\_api) | Whether to call AWS Pricing API for real or just output filter (it is useful to disable this to see filters instead of calling API) | `bool` | `true` | no | 66 | | [content](#input\_content) | JSON object containing data of Terraform plan or state | `any` | `{}` | no | 67 | | [debug\_output](#input\_debug\_output) | Whether to populate more output (useful for debug, but increase verbosity and size of tfstate) | `bool` | `false` | no | 68 | | [hourly\_price\_precision](#input\_hourly\_price\_precision) | Number of digits after comma in hourly price | `number` | `10` | no | 69 | | [monthly\_price\_precision](#input\_monthly\_price\_precision) | Number of digits after comma in monthly price | `number` | `2` | no | 70 | | [query\_all\_regions](#input\_query\_all\_regions) | If true the source will query all regions regardless of availability | `bool` | `true` | no | 71 | | [resources](#input\_resources) | Map of all resources to calculate price for | `any` | `{}` | no | 72 | 73 | ## Outputs 74 | 75 | | Name | Description | 76 | |------|-------------| 77 | | [aws\_cli\_commands](#output\_aws\_cli\_commands) | AWS CLI commands identical to AWS Pricing API calls. This should always return value (preferably one value). Adjust filters accordingly. | 78 | | [input\_resources](#output\_input\_resources) | Map of input resource filters (from plan/state or static) | 79 | | [pricing\_per\_resources](#output\_pricing\_per\_resources) | Map of resource pricing | 80 | | [pricing\_product\_filters](#output\_pricing\_product\_filters) | Map of pricing product filters (as they are submitted using data source `aws_pricing_product`) | 81 | | [resource\_quantity](#output\_resource\_quantity) | Map of resource quantity | 82 | | [resources](#output\_resources) | Map of provided resources with filters | 83 | | [total\_price\_per\_hour](#output\_total\_price\_per\_hour) | Total price for all resources per hour | 84 | | [total\_price\_per\_month](#output\_total\_price\_per\_month) | Total price for all resources per month (730 hours) | 85 | 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform AWS pricing module 2 | 3 | Terraform module, which calculates the AWS infrastructure cost in a variety of ways. This is not a _traditional_ Terraform module because it does not create AWS infrastructure resources but using Terraform plan and Terraform states as input. 4 | 5 | `cost.modules.tf` is entirely free cost estimation service, which is part of [modules.tf](https://modules.tf) that is currently in active development. 6 | 7 | Join the mailing list on [modules.tf](https://modules.tf) to stay updated! 8 | 9 | If you are looking into alternative ways calculating AWS infrastructure costs (for free), you can use [terraform-cost-estimation.com](https://www.terraform-cost-estimation.com) or [terraform-cost-estimation](https://github.com/antonbabenko/terraform-cost-estimation). 10 | 11 | This is not an official HashiCorp product. 12 | 13 | ## Supported Features 14 | 15 | - Calculate costs before creation (tfplan) 16 | - Calculate costs after creation (tfstate) 17 | - Calculate costs from multiple sources (local, remote states, specified resources) 18 | - Optionally, using [cost.modules.tf](https://cost.modules.tf/) 19 | - Can be executed on restricted CI/CD platforms where Terraform can run 20 | 21 | ## Supported Resources 22 | 23 | - EC2 instances (on-demand) and Autoscaling Groups (Launch Configurations and Launch Templates): 24 | - aws_instance 25 | - EBS Volumes, Snapshots, Snapshot Copies 26 | - aws_ebs_volume 27 | - aws_ebs_snapshot 28 | - aws_ebs_snapshot_copy 29 | - Elastic Load Balancing (ELB, ALB, NLB) 30 | - aws_elb 31 | - aws_alb / aws_lb 32 | - NAT Gateways 33 | - aws_nat_gateway 34 | - Redshift Clusters 35 | - aws_redshift_cluster 36 | 37 | ## Feature Roadmap 38 | 39 | - More EC2 instances (on-demand) and Autoscaling Groups (Launch Configurations and Launch Templates) 40 | - aws_autoscaling_group 41 | - aws_launch_configuration 42 | - aws_launch_template 43 | - EC2 Fleets (on-demand) 44 | - aws_ec2_fleet 45 | 46 | ## Usages 47 | 48 | ### Using AWS Pricing API: Terraform state or plan as JSON 49 | 50 | ```hcl 51 | provider "aws" { 52 | region = "us-east-1" 53 | } 54 | 55 | module "pricing" { 56 | source = "terraform-aws-modules/pricing/aws//modules/pricing" 57 | 58 | # content can be Terraform state or plan as JSON fetched from any source (see examples) 59 | content = jsondecode("{\"version\": 4, \"terraform_version\": \"0.14.4\", ...") 60 | } 61 | ``` 62 | 63 | ### Using AWS Pricing API: Terraform plan as JSON from local file 64 | 65 | ```hcl 66 | provider "aws" { 67 | region = "us-east-1" 68 | } 69 | 70 | data "local_file" "local_plan" { 71 | filename = "local_plan.json" 72 | } 73 | 74 | module "pricing" { 75 | source = "terraform-aws-modules/pricing/aws//modules/pricing" 76 | 77 | content = jsondecode(data.local_file.local_plan.content) 78 | } 79 | ``` 80 | 81 | ### Using AWS Pricing API: Specified resources 82 | 83 | ```hcl 84 | provider "aws" { 85 | region = "us-east-1" 86 | } 87 | 88 | module "pricing" { 89 | source = "terraform-aws-modules/pricing/aws//modules/pricing" 90 | 91 | resources = { 92 | "aws_instance.this#5" = { # Note: This means 5 instances (`count = 5`) 93 | instanceType = "c5.xlarge" 94 | location = "eu-west-2" 95 | } 96 | "aws_instance.this2" = { 97 | instanceType = "c4.xlarge" 98 | location = "eu-central-1" 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | ### Run in automation 105 | 106 | @todo: Describe in more details... 107 | 108 | ```shell 109 | # Project1 (with real EC2 resources): 110 | terraform plan -out=plan.tfplan > /dev/null && terraform show -json plan.tfplan > plan.json 111 | 112 | # Project2 (terraform-aws-pricing module): 113 | TF_VAR_file_path=plan.json terraform apply 114 | HOURLY_PRICE=$(terraform output -raw total_price_per_hour) 115 | 116 | if HOURLY_PRICE < 10 then 117 | terraform apply plan.json # (from Project1) 118 | else 119 | echo "Crash! Boom! Bang!" 120 | end 121 | ``` 122 | 123 | ### Notes 124 | 125 | #### AWS provider 126 | 127 | Set AWS provider's region to `us-east-1`, `ap-south-1` or `eu-central-1` when using [modules/pricing](https://github.com/terraform-aws-modules/terraform-aws-pricing/tree/master/modules/pricing) because AWS Pricing service is only available in these regions. 128 | 129 | You can also pass provider explicitly as described in the [official documentation](https://www.terraform.io/docs/modules/providers.html#passing-providers-explicitly). 130 | 131 | 132 | #### Debug & development tips 133 | 134 | 1. `debug_output = true` will return more output which is often helpful only for development and debug purposes. 135 | 136 | 2. `call_aws_pricing_api = false` will not call AWS Pricing API. Wrong filters produce a lot of noise, so it makes sense to disable this option when developing new filters. 137 | 138 | 3. AWS Pricing API should always return one response for the filter. Running these commands can help identify available filters to put into `modules/pricing/filters.tf` (see `dev` directory also): 139 | 140 | > aws pricing describe-services --service-code AmazonEC2 --format-version aws_v1 --max-items 1 --region us-east-1 141 | 142 | > aws pricing get-products --region us-east-1 --filters file://filters.json --format-version aws_v1 --service-code AmazonEC2 143 | 144 | 145 | #### Ephemeral Terraform backend 146 | 147 | Sometimes, you may want to not store Terraform state in backend when dealing with pricing, you can use backend "inmem": 148 | 149 | ```hcl 150 | terraform { 151 | backend "inmem" {} 152 | } 153 | ``` 154 | 155 | When you use this type of backend, there is no way to run `terraform output`. 156 | 157 | 158 | ### Known issues/limitations 159 | 160 | 1. Autoscaling groups resources 161 | 1. When changing values price is sometimes higher after the first run because it is calculated based on keys and there can be some previous keys. Solution is to update code to include some unique key/prefix. Or just disable terraform state (no state = no past). 162 | 1. At some point later, maybe add support for other providers like [Azure](http://davecallan.com/azure-price-api-examples/) and [Google Cloud](https://stackoverflow.com/questions/59048071/how-to-get-gcp-pricing-list-from-catalogue-api) 163 | 164 | 165 | ## Examples 166 | 167 | * [Cost estimation using cost.modules.tf service](https://github.com/terraform-aws-modules/terraform-aws-pricing/tree/master/examples/cost-modules-tf) 168 | * [Terraform state/plan cost estimation using AWS Pricing API](https://github.com/terraform-aws-modules/terraform-aws-pricing/tree/master/examples/pricing-terraform-state-and-plan) 169 | * [Complete example using AWS Pricing API](https://github.com/terraform-aws-modules/terraform-aws-pricing/tree/master/examples/complete) - Get cost estimation for multiple sources combined. 170 | * [Specified resources using AWS Pricing API](https://github.com/terraform-aws-modules/terraform-aws-pricing/tree/master/examples/pricing-resources) - Get cost estimation for specified resources. 171 | 172 | 173 | 174 | 175 | 176 | 177 | ## Authors 178 | 179 | Module created and managed by [Anton Babenko](https://github.com/antonbabenko). 180 | 181 | Please reach out to [Betajob](https://www.betajob.com/) if you are looking for commercial support for your Terraform, AWS, or serverless project. 182 | 183 | 184 | ## License 185 | 186 | Apache 2 Licensed. See LICENSE for full details. 187 | -------------------------------------------------------------------------------- /examples/fixtures/all-resources/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-west-1" 3 | } 4 | 5 | ####################################################### 6 | # aws_instance 7 | # aws_autoscaling_group + aws_launch_configuration + aws_launch_template + spot 8 | # aws_ec2_fleet 9 | ####################################################### 10 | 11 | resource "aws_instance" "i1" { 12 | ami = data.aws_ami.amazon_linux.id 13 | instance_type = "t3.small" 14 | } 15 | 16 | resource "aws_instance" "i2" { 17 | ami = data.aws_ami.amazon_linux.id 18 | instance_type = "c5.large" 19 | tenancy = "dedicated" 20 | } 21 | 22 | resource "aws_instance" "i3" { 23 | ami = data.aws_ami.amazon_linux.id 24 | instance_type = "c5.xlarge" 25 | tenancy = "dedicated" 26 | 27 | ebs_block_device { # "standard", "gp2", "io1", "sc1", or "st1". (Default: "gp2"). 28 | device_name = "xvfa" 29 | volume_size = 10 30 | } 31 | 32 | ebs_block_device { 33 | device_name = "xvfb" 34 | volume_size = 20 35 | volume_type = "sc1" 36 | } 37 | 38 | ebs_block_device { 39 | device_name = "xvfc" 40 | volume_size = 30 41 | volume_type = "st1" 42 | } 43 | 44 | ebs_block_device { 45 | device_name = "xvfd" 46 | volume_size = 40 47 | volume_type = "gp3" 48 | } 49 | 50 | ebs_block_device { 51 | device_name = "xvfe" 52 | volume_size = 40 53 | volume_type = "gp3" 54 | iops = 100 55 | } 56 | 57 | ebs_block_device { 58 | device_name = "xvfe" 59 | volume_size = 50 60 | volume_type = "io1" 61 | iops = 2000 62 | } 63 | 64 | root_block_device { 65 | volume_type = "io1" # "standard", "gp2", "io1", "sc1", or "st1". (Default: "standard"). 66 | iops = 220 67 | } 68 | } 69 | 70 | ####################################################### 71 | # aws_ebs_volume 72 | # aws_ebs_snapshot 73 | # aws_ebs_snapshot_copy 74 | ####################################################### 75 | 76 | resource "aws_ebs_volume" "v1" { 77 | availability_zone = "eu-west-1a" 78 | size = 10 79 | } 80 | 81 | resource "aws_ebs_volume" "v2" { 82 | availability_zone = "eu-west-1a" 83 | snapshot_id = "snapshot-123456" 84 | } 85 | 86 | resource "aws_ebs_volume" "v3" { 87 | availability_zone = "eu-west-1a" 88 | type = "sc1" 89 | size = 30 90 | } 91 | 92 | resource "aws_ebs_volume" "v4" { 93 | availability_zone = "eu-west-1a" 94 | type = "io2" 95 | iops = 100 96 | size = 50 97 | } 98 | 99 | resource "aws_ebs_volume" "v5" { 100 | availability_zone = "eu-west-1a" 101 | type = "gp3" 102 | size = 20 103 | } 104 | 105 | resource "aws_ebs_volume" "v6" { 106 | availability_zone = "eu-west-1a" 107 | type = "gp2" 108 | size = 50 109 | } 110 | 111 | resource "aws_ebs_volume" "v7" { 112 | availability_zone = "eu-west-1a" 113 | type = "io1" 114 | iops = 150 115 | size = 100 116 | } 117 | 118 | resource "aws_ebs_volume" "v8" { 119 | availability_zone = "eu-west-1a" 120 | type = "st1" 121 | size = 100 122 | } 123 | 124 | resource "aws_ebs_snapshot" "s1" { 125 | volume_id = aws_ebs_volume.v4.id 126 | } 127 | 128 | resource "aws_ebs_snapshot_copy" "sc1" { 129 | source_region = "eu-west-1" 130 | source_snapshot_id = aws_ebs_snapshot.s1.id 131 | } 132 | 133 | ####################################################### 134 | # aws_lb / aws_alb 135 | # aws_elb 136 | # aws_nat_gateway 137 | ####################################################### 138 | 139 | resource "aws_elb" "elb" { 140 | listener { 141 | instance_port = 80 142 | instance_protocol = "HTTP" 143 | lb_port = 80 144 | lb_protocol = "HTTP" 145 | } 146 | 147 | subnets = data.aws_subnets.all.ids 148 | } 149 | 150 | resource "aws_lb" "alb1" { 151 | load_balancer_type = "application" 152 | subnets = data.aws_subnets.all.ids 153 | } 154 | 155 | resource "aws_alb" "alb2" { 156 | load_balancer_type = "application" 157 | subnets = data.aws_subnets.all.ids 158 | } 159 | 160 | resource "aws_lb" "nlb" { 161 | load_balancer_type = "network" 162 | subnets = data.aws_subnets.all.ids 163 | } 164 | 165 | resource "aws_lb" "undefined_type" { 166 | subnets = data.aws_subnets.all.ids 167 | } 168 | 169 | resource "aws_eip" "nat" { 170 | domain = "vpc" 171 | } 172 | 173 | resource "aws_nat_gateway" "nat" { 174 | allocation_id = aws_eip.nat.id 175 | subnet_id = tolist(data.aws_subnets.all.ids)[0] 176 | } 177 | 178 | ####################################################### 179 | # aws_redshift_cluster 180 | ####################################################### 181 | 182 | resource "aws_redshift_cluster" "r1" { 183 | cluster_identifier = "redshift-cluster1" 184 | node_type = "dc2.large" 185 | } 186 | 187 | resource "aws_redshift_cluster" "r2" { 188 | cluster_identifier = "redshift-cluster2" 189 | node_type = "ds2.8xlarge" 190 | number_of_nodes = 3 191 | } 192 | 193 | ####################################################### 194 | # aws_db_instance 195 | ####################################################### 196 | 197 | # databaseEngine = "MySQL" 198 | resource "aws_db_instance" "d1" { 199 | engine = "mysql" 200 | instance_class = "db.t3.large" 201 | } 202 | 203 | resource "aws_db_instance" "d2" { 204 | engine = "mysql" 205 | instance_class = "db.t3.small" 206 | allocated_storage = 20 207 | storage_type = "io1" 208 | iops = 230 209 | } 210 | 211 | # databaseEngine = "PostgreSQL" 212 | resource "aws_db_instance" "d3" { 213 | engine = "postgres" 214 | instance_class = "db.t3.small" 215 | allocated_storage = 10 216 | storage_type = "gp2" 217 | } 218 | 219 | resource "aws_db_instance" "d4" { 220 | engine = "postgres" 221 | instance_class = "db.t3.small" 222 | allocated_storage = 10 223 | storage_type = "gp2" 224 | multi_az = true 225 | } 226 | 227 | # databaseEngine = "SQL Server" 228 | # databaseEdition = "Express" 229 | resource "aws_db_instance" "d5" { 230 | engine = "sqlserver-ex" 231 | instance_class = "db.t3.medium" 232 | allocated_storage = 30 233 | storage_type = "gp2" 234 | } 235 | 236 | # databaseEngine = "SQL Server" 237 | # databaseEdition = "Enterprise" 238 | resource "aws_db_instance" "d6" { 239 | engine = "sqlserver-ee" 240 | instance_class = "db.r5.xlarge" 241 | allocated_storage = 30 242 | storage_type = "gp2" 243 | } 244 | 245 | # databaseEngine = "SQL Server" 246 | # databaseEdition = "Standard" 247 | resource "aws_db_instance" "d7" { 248 | engine = "sqlserver-se" 249 | instance_class = "db.r5.xlarge" 250 | allocated_storage = 30 251 | storage_type = "gp2" 252 | } 253 | 254 | # databaseEngine = "SQL Server" 255 | # databaseEdition = "Web" 256 | resource "aws_db_instance" "d8" { 257 | engine = "sqlserver-web" 258 | instance_class = "db.r5.xlarge" 259 | allocated_storage = 30 260 | storage_type = "gp2" 261 | } 262 | 263 | # databaseEngine = "Oracle" 264 | resource "aws_db_instance" "d9" { 265 | engine = "oracle-ee" 266 | instance_class = "db.t3.large" 267 | allocated_storage = 40 268 | storage_type = "gp2" 269 | } 270 | 271 | # databaseEngine = "MariaDB" 272 | resource "aws_db_instance" "d10" { 273 | engine = "mariadb" 274 | instance_class = "db.t3.large" 275 | allocated_storage = 40 276 | storage_type = "gp2" 277 | } 278 | -------------------------------------------------------------------------------- /examples/fixtures/etc/plan-ebs-volume.json: -------------------------------------------------------------------------------- 1 | { 2 | "format_version": "0.1", 3 | "terraform_version": "0.14.4", 4 | "planned_values": { 5 | "root_module": { 6 | "resources": [ 7 | { 8 | "address": "aws_ebs_volume.v1", 9 | "mode": "managed", 10 | "type": "aws_ebs_volume", 11 | "name": "v1", 12 | "provider_name": "registry.terraform.io/hashicorp/aws", 13 | "schema_version": 0, 14 | "values": { 15 | "availability_zone": "eu-central-1a", 16 | "multi_attach_enabled": null, 17 | "outpost_arn": null, 18 | "size": 10, 19 | "tags": null 20 | } 21 | }, 22 | { 23 | "address": "aws_ebs_volume.v2", 24 | "mode": "managed", 25 | "type": "aws_ebs_volume", 26 | "name": "v2", 27 | "provider_name": "registry.terraform.io/hashicorp/aws", 28 | "schema_version": 0, 29 | "values": { 30 | "availability_zone": "eu-west-2b", 31 | "multi_attach_enabled": null, 32 | "outpost_arn": null, 33 | "tags": null 34 | } 35 | }, 36 | { 37 | "address": "aws_ebs_volume.v3", 38 | "mode": "managed", 39 | "type": "aws_ebs_volume", 40 | "name": "v3", 41 | "provider_name": "registry.terraform.io/hashicorp/aws", 42 | "schema_version": 0, 43 | "values": { 44 | "availability_zone": "eu-south-1a", 45 | "multi_attach_enabled": null, 46 | "outpost_arn": null, 47 | "tags": null, 48 | "type": "sc1" 49 | } 50 | }, 51 | { 52 | "address": "aws_ebs_volume.v4", 53 | "mode": "managed", 54 | "type": "aws_ebs_volume", 55 | "name": "v4", 56 | "provider_name": "registry.terraform.io/hashicorp/aws", 57 | "schema_version": 0, 58 | "values": { 59 | "availability_zone": "eu-west-1a", 60 | "iops": 370, 61 | "multi_attach_enabled": null, 62 | "outpost_arn": null, 63 | "tags": null, 64 | "type": "io2" 65 | } 66 | } 67 | ] 68 | } 69 | }, 70 | "resource_changes": [ 71 | { 72 | "address": "aws_ebs_volume.v1", 73 | "mode": "managed", 74 | "type": "aws_ebs_volume", 75 | "name": "v1", 76 | "provider_name": "registry.terraform.io/hashicorp/aws", 77 | "change": { 78 | "actions": [ 79 | "create" 80 | ], 81 | "before": null, 82 | "after": { 83 | "availability_zone": "eu-west-1a", 84 | "multi_attach_enabled": null, 85 | "outpost_arn": null, 86 | "size": 10, 87 | "tags": null 88 | }, 89 | "after_unknown": { 90 | "arn": true, 91 | "encrypted": true, 92 | "id": true, 93 | "iops": true, 94 | "kms_key_id": true, 95 | "snapshot_id": true, 96 | "type": true 97 | } 98 | } 99 | }, 100 | { 101 | "address": "aws_ebs_volume.v2", 102 | "mode": "managed", 103 | "type": "aws_ebs_volume", 104 | "name": "v2", 105 | "provider_name": "registry.terraform.io/hashicorp/aws", 106 | "change": { 107 | "actions": [ 108 | "create" 109 | ], 110 | "before": null, 111 | "after": { 112 | "availability_zone": "eu-west-1a", 113 | "multi_attach_enabled": null, 114 | "outpost_arn": null, 115 | "tags": null 116 | }, 117 | "after_unknown": { 118 | "arn": true, 119 | "encrypted": true, 120 | "id": true, 121 | "iops": true, 122 | "kms_key_id": true, 123 | "size": true, 124 | "snapshot_id": true, 125 | "type": true 126 | } 127 | } 128 | }, 129 | { 130 | "address": "aws_ebs_volume.v3", 131 | "mode": "managed", 132 | "type": "aws_ebs_volume", 133 | "name": "v3", 134 | "provider_name": "registry.terraform.io/hashicorp/aws", 135 | "change": { 136 | "actions": [ 137 | "create" 138 | ], 139 | "before": null, 140 | "after": { 141 | "availability_zone": "eu-west-1a", 142 | "multi_attach_enabled": null, 143 | "outpost_arn": null, 144 | "tags": null, 145 | "type": "sc1" 146 | }, 147 | "after_unknown": { 148 | "arn": true, 149 | "encrypted": true, 150 | "id": true, 151 | "iops": true, 152 | "kms_key_id": true, 153 | "size": true, 154 | "snapshot_id": true 155 | } 156 | } 157 | }, 158 | { 159 | "address": "aws_ebs_volume.v4", 160 | "mode": "managed", 161 | "type": "aws_ebs_volume", 162 | "name": "v4", 163 | "provider_name": "registry.terraform.io/hashicorp/aws", 164 | "change": { 165 | "actions": [ 166 | "create" 167 | ], 168 | "before": null, 169 | "after": { 170 | "availability_zone": "eu-west-1a", 171 | "iops": 100, 172 | "multi_attach_enabled": null, 173 | "outpost_arn": null, 174 | "tags": null, 175 | "type": "io2" 176 | }, 177 | "after_unknown": { 178 | "arn": true, 179 | "encrypted": true, 180 | "id": true, 181 | "kms_key_id": true, 182 | "size": true, 183 | "snapshot_id": true 184 | } 185 | } 186 | } 187 | ], 188 | "configuration": { 189 | "provider_config": { 190 | "aws": { 191 | "name": "aws", 192 | "expressions": { 193 | "region": { 194 | "constant_value": "eu-west-1" 195 | } 196 | } 197 | } 198 | }, 199 | "root_module": { 200 | "resources": [ 201 | { 202 | "address": "aws_ebs_volume.v1", 203 | "mode": "managed", 204 | "type": "aws_ebs_volume", 205 | "name": "v1", 206 | "provider_config_key": "aws", 207 | "expressions": { 208 | "availability_zone": { 209 | "constant_value": "eu-west-1a" 210 | }, 211 | "size": { 212 | "constant_value": 10 213 | } 214 | }, 215 | "schema_version": 0 216 | }, 217 | { 218 | "address": "aws_ebs_volume.v2", 219 | "mode": "managed", 220 | "type": "aws_ebs_volume", 221 | "name": "v2", 222 | "provider_config_key": "aws", 223 | "expressions": { 224 | "availability_zone": { 225 | "constant_value": "eu-west-1a" 226 | } 227 | }, 228 | "schema_version": 0 229 | }, 230 | { 231 | "address": "aws_ebs_volume.v3", 232 | "mode": "managed", 233 | "type": "aws_ebs_volume", 234 | "name": "v3", 235 | "provider_config_key": "aws", 236 | "expressions": { 237 | "availability_zone": { 238 | "constant_value": "eu-west-1a" 239 | }, 240 | "type": { 241 | "constant_value": "sc1" 242 | } 243 | }, 244 | "schema_version": 0 245 | }, 246 | { 247 | "address": "aws_ebs_volume.v4", 248 | "mode": "managed", 249 | "type": "aws_ebs_volume", 250 | "name": "v4", 251 | "provider_config_key": "aws", 252 | "expressions": { 253 | "availability_zone": { 254 | "constant_value": "eu-west-1a" 255 | }, 256 | "iops": { 257 | "constant_value": 100 258 | }, 259 | "type": { 260 | "constant_value": "io2" 261 | } 262 | }, 263 | "schema_version": 0 264 | } 265 | ] 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /modules/pricing/state_and_plan_content_from_json.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | 3 | # JSON object of plan or state as content 4 | input_type = length(keys(local.content)) == 0 ? "empty" : (contains(keys(local.content), "resources") ? "state" : "plan") 5 | 6 | # State - all resources are linear 7 | # Plan - combine root_module.resources and root_module.child_modules 8 | content_resources_state = local.input_type == "state" ? jsonencode(local.content.resources) : jsonencode({}) 9 | content_resources_plan = local.input_type == "plan" ? jsonencode(concat(lookup(local.content.planned_values.root_module, "resources", []), flatten(concat([for m in lookup(local.content.planned_values.root_module, "child_modules", {}) : m.resources])))) : jsonencode({}) 10 | 11 | content_resources_string = local.input_type == "plan" ? local.content_resources_plan : local.content_resources_state 12 | 13 | content_resources = jsondecode(local.content_resources_string) 14 | 15 | # Removing resources which we don't support 16 | instances = [ 17 | for r in local.content_resources : 18 | r 19 | if local.input_type != "empty" && length(lookup(r, "instances", ["plan-has-no-instances-key"])) > 0 && r.mode == "managed" && contains(keys(local.resource_types), r.type) 20 | ] 21 | 22 | # Normalize structure when input is "plan" into: 23 | # { "aws_instance.this.module.module_count_2[1].aws_instance.this[0]" = { 24 | # "0" = { 25 | # "_type" = "aws_instance" 26 | # "instance_type" = "t2.nano" 27 | # } 28 | # } 29 | normalized_plan_instances = local.input_type == "plan" ? { 30 | for v in local.instances : 31 | join(".", compact([v.type, v.name, v.address])) => { 32 | 0 : ( 33 | jsonencode(merge(v.values, { "_type" : v.type })) 34 | ) 35 | } 36 | } : {} 37 | 38 | # Normalize structure when input is "state" into: 39 | # { "aws_instance.this.module.module_count_2[1]" = { 40 | # "0" = "{\"instance_type\":\"t2.nano\",\"_type\":\"aws_instance\"}" 41 | # } 42 | # Note: 43 | # v.attributes is an object which means we can't just `merge()` as we do for "plan"-case, 44 | # Error is produced "Invalid value for "v" parameter: cannot convert object to map of any single type." 45 | # The hacky solution is to call `jsonencode(v.attributes)` and append element "_type" as string to the end of the encoded object. 46 | normalized_state_instances = local.input_type == "state" ? { 47 | for i in local.instances : 48 | join(".", compact([i.type, i.name, lookup(i, "module", "")])) => { 49 | for v in i.instances : 50 | lookup(v, "index_key", 0) => ( 51 | replace(jsonencode(v.attributes), "/}$/", ",\"_type\":\"${i.type}\"}") 52 | ) 53 | } 54 | } : {} 55 | 56 | instances_map = local.input_type == "plan" ? local.normalized_plan_instances : local.normalized_state_instances 57 | 58 | instances_stripped_keys = local.input_type == "empty" || length(local.instances_map) == 0 ? [] : concat([ 59 | for k, v in local.instances_map : 60 | setproduct([k], keys(v)) 61 | ]...) 62 | 63 | # Prepare value to apply filters and transforms by jsondecoding values 64 | instances_map_fixed_keys = { 65 | for k in local.instances_stripped_keys : 66 | format("%s[%s]", k[0], k[1]) => ( 67 | jsondecode(local.instances_map[k[0]][k[1]]) 68 | ) 69 | if try(length(regex(lookup(local.local_dev, "process_keys_regex", ".*"), k[0])) > 0, false) 70 | } 71 | 72 | 73 | # Extract filters from fields with type "list" into canonical structure 74 | extracted_filters_from_list_type_fields = merge(flatten([ 75 | for k, v in local.instances_map_fixed_keys : 76 | [ 77 | for field_k, field_v in v : 78 | [ 79 | for num, item_fields in field_v : 80 | [ 81 | for filter_k, filter_v in local.resource_types[v._type][field_k] : { 82 | join(".", [filter_v.new_resource_type, num, field_k, k]) : merge({ 83 | "_type" : filter_v.new_resource_type 84 | }, { 85 | for i_k, i_v in lookup(filter_v, "map_values", {}) : 86 | i_k => lookup(item_fields, i_v, null) 87 | }) 88 | } 89 | if lookup(filter_v, "required_field", null) == null || try(lookup(item_fields, lookup(filter_v, "required_field", null)), null) != null 90 | ] 91 | ] 92 | if !can(tostring(field_v)) && contains(keys(local.resource_types[v._type]), field_k) 93 | ] 94 | ])...) 95 | 96 | 97 | # Extract filters from fields with scalar type values (string, number) into canonical structure 98 | extracted_filters_from_scalar_type_fields = merge(flatten([ 99 | for k, v in local.instances_map_fixed_keys : 100 | [ 101 | for field_k, field_v in v : 102 | { 103 | for filter_k, filter_v in local.resource_types[v._type][field_k] : 104 | join(".", [filter_v.new_resource_type, filter_k, field_k, k]) => merge({ 105 | "_type" : filter_v.new_resource_type 106 | }, { 107 | for i_k, i_v in lookup(filter_v, "map_values", {}) : 108 | i_k => lookup(local.instances_map_fixed_keys[k], i_v, null) 109 | }) 110 | if lookup(filter_v, "required_field", null) == null || try(lookup(local.instances_map_fixed_keys[k], lookup(filter_v, "required_field", null)), null) != null 111 | } 112 | if can(tostring(field_v)) && contains(keys(local.resource_types[v._type]), field_k) && try(length(local.resource_types[v._type][field_k][0].new_resource_type) > 0, false) 113 | ] 114 | ])...) 115 | 116 | 117 | # Remove irrelevant attributes (for main filter) and after filter has been expanded into separate resource (eg, EBS volume from EC2 instances) 118 | instance_details_filtered = { 119 | for k, v in local.instances_map_fixed_keys : 120 | k => { 121 | for field_k, field_v in v : 122 | field_k => field_v 123 | if contains(concat(keys(local.resource_types[v._type]), ["_type"]), field_k) && try(length(local.resource_types[v._type][field_k][0]) == 0, true) 124 | } 125 | } 126 | 127 | 128 | # Merge all extracted filters 129 | all_details = merge(local.instance_details_filtered, local.extracted_filters_from_list_type_fields, local.extracted_filters_from_scalar_type_fields) 130 | 131 | # Transform field values and store with correct filter names as key 132 | instance_filters = { 133 | for k, v in local.all_details : 134 | k => { 135 | for field_k, field_v in v : 136 | lookup(local.resource_types[v._type][field_k], "filter", field_k) => ( 137 | 138 | # get_region_from_arn 139 | lookup(local.resource_types[v._type][field_k], "transformer", null) == "get_region_from_arn" ? try(split(":", field_v)[3], var.aws_default_region) : 140 | 141 | # get_region_from_arn_with_lookup_lb_usagetype 142 | lookup(local.resource_types[v._type][field_k], "transformer", null) == "get_region_from_arn_with_lookup_lb_usagetype" ? lookup(local.lb_usagetype, try(split(":", field_v)[3], var.aws_default_region), "LoadBalancerUsage") : 143 | 144 | # get_region_from_arn_with_lookup_ebs_snapshot_usagetype 145 | lookup(local.resource_types[v._type][field_k], "transformer", null) == "get_region_from_arn_with_lookup_ebs_snapshot_usagetype" ? lookup(local.ebs_snapshot_usagetype, var.aws_default_region, "EBS:SnapshotUsage") : 146 | 147 | # cut_region_from_availability_zone 148 | lookup(local.resource_types[v._type][field_k], "transformer", null) == "get_region_from_availability_zone" ? try(substr(field_v, 0, length(field_v) - 1), var.aws_default_region) : 149 | 150 | # get_instance_tenancy 151 | lookup(local.resource_types[v._type][field_k], "transformer", null) == "get_instance_tenancy" ? (field_v == "dedicated" ? "Dedicated" : "Shared") : 152 | 153 | # get_load_balancer_type 154 | lookup(local.resource_types[v._type][field_k], "transformer", null) == "get_load_balancer_type" ? (field_v == "application" ? "Load Balancer-Application" : "Load Balancer-Network") : 155 | 156 | # get_db_instance_engine 157 | lookup(local.resource_types[v._type][field_k], "transformer", null) == "get_db_instance_engine" ? (lookup(local.db_instance_engine, field_v)) : 158 | 159 | # get_db_instance_engine_edition 160 | lookup(local.resource_types[v._type][field_k], "transformer", null) == "get_db_instance_engine_edition" ? (lookup(local.db_instance_engine_edition, local.all_details[k]["engine"], null)) : 161 | 162 | # get_multi_az 163 | lookup(local.resource_types[v._type][field_k], "transformer", null) == "get_multi_az" ? (field_v == true ? "Multi-AZ" : "Single-AZ") : 164 | 165 | # Check if value is null -> use default value for this resource 166 | field_v == null && contains(keys(local.resource_defaults[v._type]), lookup(local.resource_types[v._type][field_k], "filter", field_k)) ? lookup(local.resource_defaults[v._type], lookup(local.resource_types[v._type][field_k], "filter", field_k), "Missing default value") : 167 | 168 | # Default, field_v can be string or list (jsonencode to string for certainty) 169 | try(tostring(field_v), jsonencode(field_v)) 170 | 171 | ) if contains(keys(local.resource_types[v._type]), field_k) 172 | } 173 | } 174 | 175 | # Remove keys where filter's value is null 176 | instance_final_filters = { 177 | for k, v in local.instance_filters : 178 | k => { 179 | for filter_k, filter_v in v : 180 | filter_k => filter_v 181 | if filter_v != null 182 | } 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /examples/fixtures/etc/plan-instance-with-multiple-ebs.json: -------------------------------------------------------------------------------- 1 | { 2 | "format_version": "0.1", 3 | "terraform_version": "0.12.24", 4 | "planned_values": { 5 | "root_module": { 6 | "resources": [ 7 | { 8 | "address": "aws_instance.this_1", 9 | "mode": "managed", 10 | "type": "aws_instance", 11 | "name": "this_1", 12 | "provider_name": "aws", 13 | "schema_version": 1, 14 | "values": { 15 | "ami": "ami-06ce3edf0cff21f07", 16 | "credit_specification": [], 17 | "disable_api_termination": null, 18 | "ebs_block_device": [ 19 | { 20 | "delete_on_termination": true, 21 | "device_name": "xvfa", 22 | "volume_size": 20, 23 | "volume_type": "sc1" 24 | }, 25 | { 26 | "delete_on_termination": true, 27 | "device_name": "xvfb", 28 | "iops": 2000, 29 | "volume_size": 30, 30 | "volume_type": "io1" 31 | }, 32 | { 33 | "delete_on_termination": true, 34 | "device_name": "xvfs", 35 | "volume_size": 10 36 | } 37 | ], 38 | "ebs_optimized": null, 39 | "get_password_data": false, 40 | "hibernation": null, 41 | "iam_instance_profile": null, 42 | "instance_initiated_shutdown_behavior": null, 43 | "instance_type": "t3.nano", 44 | "monitoring": null, 45 | "root_block_device": [ 46 | { 47 | "delete_on_termination": true, 48 | "iops": 220, 49 | "volume_type": "io1" 50 | } 51 | ], 52 | "source_dest_check": true, 53 | "tags": null, 54 | "timeouts": null, 55 | "user_data": null, 56 | "user_data_base64": null 57 | } 58 | } 59 | ] 60 | } 61 | }, 62 | "resource_changes": [ 63 | { 64 | "address": "aws_ebs_snapshot.standard", 65 | "mode": "managed", 66 | "type": "aws_ebs_snapshot", 67 | "name": "standard", 68 | "provider_name": "aws", 69 | "change": { 70 | "actions": [ 71 | "create" 72 | ], 73 | "before": null, 74 | "after": { 75 | "description": null, 76 | "tags": null, 77 | "timeouts": null 78 | }, 79 | "after_unknown": { 80 | "data_encryption_key_id": true, 81 | "encrypted": true, 82 | "id": true, 83 | "kms_key_id": true, 84 | "owner_alias": true, 85 | "owner_id": true, 86 | "volume_id": true, 87 | "volume_size": true 88 | } 89 | } 90 | }, 91 | { 92 | "address": "aws_ebs_snapshot_copy.copied", 93 | "mode": "managed", 94 | "type": "aws_ebs_snapshot_copy", 95 | "name": "copied", 96 | "provider_name": "aws", 97 | "change": { 98 | "actions": [ 99 | "create" 100 | ], 101 | "before": null, 102 | "after": { 103 | "description": null, 104 | "encrypted": null, 105 | "kms_key_id": null, 106 | "source_region": "eu-west-1", 107 | "tags": null 108 | }, 109 | "after_unknown": { 110 | "data_encryption_key_id": true, 111 | "id": true, 112 | "owner_alias": true, 113 | "owner_id": true, 114 | "source_snapshot_id": true, 115 | "volume_id": true, 116 | "volume_size": true 117 | } 118 | } 119 | }, 120 | { 121 | "address": "aws_ebs_volume.volume_io1", 122 | "mode": "managed", 123 | "type": "aws_ebs_volume", 124 | "name": "volume_io1", 125 | "provider_name": "aws", 126 | "change": { 127 | "actions": [ 128 | "create" 129 | ], 130 | "before": null, 131 | "after": { 132 | "availability_zone": "eu-west-1a", 133 | "outpost_arn": null, 134 | "size": 8, 135 | "tags": null, 136 | "type": "io1" 137 | }, 138 | "after_unknown": { 139 | "arn": true, 140 | "encrypted": true, 141 | "id": true, 142 | "iops": true, 143 | "kms_key_id": true, 144 | "snapshot_id": true 145 | } 146 | } 147 | }, 148 | { 149 | "address": "aws_ebs_volume.volume_standard", 150 | "mode": "managed", 151 | "type": "aws_ebs_volume", 152 | "name": "volume_standard", 153 | "provider_name": "aws", 154 | "change": { 155 | "actions": [ 156 | "create" 157 | ], 158 | "before": null, 159 | "after": { 160 | "availability_zone": "eu-west-1a", 161 | "outpost_arn": null, 162 | "size": 7, 163 | "tags": null, 164 | "type": "standard" 165 | }, 166 | "after_unknown": { 167 | "arn": true, 168 | "encrypted": true, 169 | "id": true, 170 | "iops": true, 171 | "kms_key_id": true, 172 | "snapshot_id": true 173 | } 174 | } 175 | }, 176 | { 177 | "address": "aws_instance.this_1", 178 | "mode": "managed", 179 | "type": "aws_instance", 180 | "name": "this_1", 181 | "provider_name": "aws", 182 | "change": { 183 | "actions": [ 184 | "create" 185 | ], 186 | "before": null, 187 | "after": { 188 | "ami": "ami-06ce3edf0cff21f07", 189 | "credit_specification": [], 190 | "disable_api_termination": null, 191 | "ebs_block_device": [ 192 | { 193 | "delete_on_termination": true, 194 | "device_name": "xvfa", 195 | "volume_size": 20, 196 | "volume_type": "sc1" 197 | }, 198 | { 199 | "delete_on_termination": true, 200 | "device_name": "xvfb", 201 | "iops": 2000, 202 | "volume_size": 30, 203 | "volume_type": "io1" 204 | }, 205 | { 206 | "delete_on_termination": true, 207 | "device_name": "xvfs", 208 | "volume_size": 10 209 | } 210 | ], 211 | "ebs_optimized": null, 212 | "get_password_data": false, 213 | "hibernation": null, 214 | "iam_instance_profile": null, 215 | "instance_initiated_shutdown_behavior": null, 216 | "instance_type": "t3.nano", 217 | "monitoring": null, 218 | "root_block_device": [ 219 | { 220 | "delete_on_termination": true, 221 | "iops": 220, 222 | "volume_type": "io1" 223 | } 224 | ], 225 | "source_dest_check": true, 226 | "tags": null, 227 | "timeouts": null, 228 | "user_data": null, 229 | "user_data_base64": null 230 | }, 231 | "after_unknown": { 232 | "arn": true, 233 | "associate_public_ip_address": true, 234 | "availability_zone": true, 235 | "cpu_core_count": true, 236 | "cpu_threads_per_core": true, 237 | "credit_specification": [], 238 | "ebs_block_device": [ 239 | { 240 | "encrypted": true, 241 | "iops": true, 242 | "kms_key_id": true, 243 | "snapshot_id": true, 244 | "volume_id": true 245 | }, 246 | { 247 | "encrypted": true, 248 | "kms_key_id": true, 249 | "snapshot_id": true, 250 | "volume_id": true 251 | }, 252 | { 253 | "encrypted": true, 254 | "iops": true, 255 | "kms_key_id": true, 256 | "snapshot_id": true, 257 | "volume_id": true, 258 | "volume_type": true 259 | } 260 | ], 261 | "ephemeral_block_device": true, 262 | "host_id": true, 263 | "id": true, 264 | "instance_state": true, 265 | "ipv6_address_count": true, 266 | "ipv6_addresses": true, 267 | "key_name": true, 268 | "metadata_options": true, 269 | "network_interface": true, 270 | "network_interface_id": true, 271 | "outpost_arn": true, 272 | "password_data": true, 273 | "placement_group": true, 274 | "primary_network_interface_id": true, 275 | "private_dns": true, 276 | "private_ip": true, 277 | "public_dns": true, 278 | "public_ip": true, 279 | "root_block_device": [ 280 | { 281 | "device_name": true, 282 | "encrypted": true, 283 | "kms_key_id": true, 284 | "volume_id": true, 285 | "volume_size": true 286 | } 287 | ], 288 | "security_groups": true, 289 | "subnet_id": true, 290 | "tenancy": true, 291 | "volume_tags": true, 292 | "vpc_security_group_ids": true 293 | } 294 | } 295 | } 296 | ], 297 | "configuration": { 298 | "root_module": { 299 | "resources": [ 300 | { 301 | "address": "aws_ebs_snapshot.standard", 302 | "mode": "managed", 303 | "type": "aws_ebs_snapshot", 304 | "name": "standard", 305 | "provider_config_key": "aws", 306 | "expressions": { 307 | "volume_id": { 308 | "references": [ 309 | "aws_ebs_volume.volume_standard" 310 | ] 311 | } 312 | }, 313 | "schema_version": 0 314 | }, 315 | { 316 | "address": "aws_ebs_snapshot_copy.copied", 317 | "mode": "managed", 318 | "type": "aws_ebs_snapshot_copy", 319 | "name": "copied", 320 | "provider_config_key": "aws", 321 | "expressions": { 322 | "source_region": { 323 | "constant_value": "eu-west-1" 324 | }, 325 | "source_snapshot_id": { 326 | "references": [ 327 | "aws_ebs_snapshot.standard" 328 | ] 329 | } 330 | }, 331 | "schema_version": 0 332 | }, 333 | { 334 | "address": "aws_ebs_volume.volume_io1", 335 | "mode": "managed", 336 | "type": "aws_ebs_volume", 337 | "name": "volume_io1", 338 | "provider_config_key": "aws", 339 | "expressions": { 340 | "availability_zone": { 341 | "constant_value": "eu-west-1a" 342 | }, 343 | "size": { 344 | "constant_value": 8 345 | }, 346 | "type": { 347 | "constant_value": "io1" 348 | } 349 | }, 350 | "schema_version": 0 351 | }, 352 | { 353 | "address": "aws_ebs_volume.volume_standard", 354 | "mode": "managed", 355 | "type": "aws_ebs_volume", 356 | "name": "volume_standard", 357 | "provider_config_key": "aws", 358 | "expressions": { 359 | "availability_zone": { 360 | "constant_value": "eu-west-1a" 361 | }, 362 | "size": { 363 | "constant_value": 7 364 | }, 365 | "type": { 366 | "constant_value": "standard" 367 | } 368 | }, 369 | "schema_version": 0 370 | }, 371 | { 372 | "address": "aws_instance.this_1", 373 | "mode": "managed", 374 | "type": "aws_instance", 375 | "name": "this_1", 376 | "provider_config_key": "aws", 377 | "expressions": { 378 | "ami": { 379 | "constant_value": "ami-06ce3edf0cff21f07" 380 | }, 381 | "ebs_block_device": [ 382 | { 383 | "device_name": { 384 | "constant_value": "xvfs" 385 | }, 386 | "volume_size": { 387 | "constant_value": 10 388 | } 389 | }, 390 | { 391 | "device_name": { 392 | "constant_value": "xvfa" 393 | }, 394 | "volume_size": { 395 | "constant_value": 20 396 | }, 397 | "volume_type": { 398 | "constant_value": "sc1" 399 | } 400 | }, 401 | { 402 | "device_name": { 403 | "constant_value": "xvfb" 404 | }, 405 | "iops": { 406 | "constant_value": 2000 407 | }, 408 | "volume_size": { 409 | "constant_value": 30 410 | }, 411 | "volume_type": { 412 | "constant_value": "io1" 413 | } 414 | } 415 | ], 416 | "instance_type": { 417 | "constant_value": "t3.nano" 418 | }, 419 | "root_block_device": [ 420 | { 421 | "iops": { 422 | "constant_value": 220 423 | }, 424 | "volume_type": { 425 | "constant_value": "io1" 426 | } 427 | } 428 | ] 429 | }, 430 | "schema_version": 1 431 | } 432 | ] 433 | } 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /modules/pricing/filters.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | # Include all attributes to keep as filters 3 | resource_types = { # resource's type => input's keys + transformers => save in key "filter" 4 | 5 | aws_instance = { 6 | arn = { 7 | transformer = "get_region_from_arn" 8 | filter = "location" 9 | } 10 | instance_type = { 11 | filter = "instanceType" 12 | } 13 | tenancy = { 14 | transformer = "get_instance_tenancy" 15 | } 16 | 17 | ebs_block_device = [{ 18 | new_resource_type = "aws_ebs_volume" 19 | map_values = { 20 | size = "volume_size" 21 | type = "volume_type" 22 | } 23 | }, { 24 | required_field = "iops" 25 | new_resource_type = "_aws_ebs_provisioned_iops" 26 | map_values = { 27 | type = "volume_type" 28 | _quantity = "iops" 29 | } 30 | }] 31 | 32 | root_block_device = [{ 33 | new_resource_type = "aws_ebs_volume" 34 | map_values = { 35 | size = "volume_size" 36 | type = "volume_type" 37 | } 38 | }, { 39 | required_field = "iops" 40 | new_resource_type = "_aws_ebs_provisioned_iops" 41 | map_values = { 42 | type = "volume_type" 43 | _quantity = "iops" 44 | } 45 | }] 46 | } 47 | 48 | 49 | aws_ebs_volume = { 50 | availability_zone = { 51 | transformer = "get_region_from_availability_zone" 52 | filter = "location" 53 | } 54 | type = { 55 | filter = "volumeApiName" 56 | } 57 | size = { 58 | filter = "_quantity" 59 | } 60 | 61 | # Note: Type of the value here should be `list(map)`, one or several subfilters will be created. 62 | iops = [{ 63 | required_field = "iops" 64 | new_resource_type = "_aws_ebs_provisioned_iops" 65 | map_values = { 66 | type = "type" 67 | _quantity = "iops" 68 | } 69 | }] 70 | 71 | } 72 | 73 | aws_ebs_snapshot = { 74 | arn = { 75 | transformer = "get_region_from_arn_with_lookup_ebs_snapshot_usagetype" 76 | filter = "usagetype" 77 | } 78 | volume_size = { 79 | filter = "_quantity" 80 | } 81 | } 82 | 83 | aws_ebs_snapshot_copy = { 84 | arn = { 85 | transformer = "get_region_from_arn_with_lookup_ebs_snapshot_usagetype" 86 | filter = "usagetype" 87 | } 88 | volume_size = { 89 | filter = "_quantity" 90 | } 91 | } 92 | 93 | aws_lb = { 94 | arn = { 95 | transformer = "get_region_from_arn_with_lookup_lb_usagetype" 96 | filter = "usagetype" 97 | } 98 | load_balancer_type = { 99 | transformer = "get_load_balancer_type" 100 | filter = "productFamily" 101 | } 102 | } 103 | 104 | aws_alb = { 105 | arn = { 106 | transformer = "get_region_from_arn_with_lookup_lb_usagetype" 107 | filter = "usagetype" 108 | } 109 | load_balancer_type = { 110 | transformer = "get_load_balancer_type" 111 | filter = "productFamily" 112 | } 113 | } 114 | 115 | aws_elb = { 116 | arn = { 117 | transformer = "get_region_from_arn_with_lookup_lb_usagetype" 118 | filter = "usagetype" 119 | } 120 | } 121 | 122 | aws_nat_gateway = {} 123 | 124 | aws_redshift_cluster = { 125 | node_type = { 126 | filter = "instanceType" 127 | } 128 | number_of_nodes = { 129 | filter = "_quantity" 130 | } 131 | } 132 | 133 | aws_db_instance = { 134 | instance_class = { 135 | filter = "instanceType" 136 | } 137 | multi_az = { 138 | transformer = "get_multi_az" 139 | filter = "deploymentOption" 140 | } 141 | engine = { 142 | transformer = "get_db_instance_engine" 143 | filter = "databaseEngine" 144 | } 145 | # Note: Key (eg, deletion_protection) should exist in resource but not used in other filters 146 | deletion_protection = { 147 | transformer = "get_db_instance_engine_edition" 148 | filter = "databaseEdition" 149 | } 150 | 151 | } 152 | 153 | ########################################## 154 | # Custom types (produced from main types) 155 | ########################################## 156 | _aws_ebs_provisioned_iops = {} 157 | 158 | } 159 | 160 | # Map with default filters for each resource type (it will be merged with the data provided) 161 | resource_defaults = { 162 | aws_instance = { 163 | location = var.aws_default_region 164 | operatingSystem = "Linux" 165 | preInstalledSw = "NA" 166 | licenseModel = "No License required" 167 | tenancy = "Shared" 168 | capacitystatus = "Used" 169 | } 170 | 171 | aws_ebs_volume = { 172 | location = var.aws_default_region 173 | productFamily = "Storage" 174 | volumeApiName = var.aws_default_ebs_volume_type 175 | _quantity = var.aws_default_ebs_volume_size 176 | _divisor = 730 # Storage has unit GB-Mo, so converting to price per hour 177 | } 178 | 179 | _aws_ebs_provisioned_iops = { 180 | location = var.aws_default_region 181 | productFamily = "System Operation" 182 | group = "EBS IOPS" 183 | volumeApiName = "gp3" 184 | _quantity = var.aws_default_ebs_volume_size 185 | _divisor = 730 # PIOPS has unit IOPS-month, so converting to price per hour 186 | } 187 | 188 | # _aws_ebs_provisioned_storage = { # @todo 189 | # location = var.aws_default_region 190 | # productFamily = "System Operation" 191 | # volumeApiName = "gp3" 192 | # _quantity = var.aws_default_ebs_volume_size 193 | # _divisor = 730 # Storage has unit GB-Mo, so converting to price per hour 194 | # } 195 | 196 | aws_ebs_snapshot = { 197 | location = var.aws_default_region 198 | productFamily = "Storage Snapshot" 199 | usagetype = lookup(local.ebs_snapshot_usagetype, var.aws_default_region, "EBS:SnapshotUsage") 200 | _quantity = var.aws_default_ebs_volume_size 201 | _divisor = 730 # Storage has unit GB-Mo, so converting to price per hour 202 | } 203 | 204 | aws_ebs_snapshot_copy = { 205 | location = var.aws_default_region 206 | productFamily = "Storage Snapshot" 207 | usagetype = lookup(local.ebs_snapshot_usagetype, var.aws_default_region, "EBS:SnapshotUsage") 208 | _quantity = var.aws_default_ebs_volume_size 209 | _divisor = 730 # Storage has unit GB-Mo, so converting to price per hour 210 | } 211 | 212 | aws_lb = { 213 | usagetype = lookup(local.lb_usagetype, var.aws_default_region, "LoadBalancerUsage") 214 | } 215 | 216 | aws_alb = { 217 | usagetype = lookup(local.lb_usagetype, var.aws_default_region, "LoadBalancerUsage") 218 | } 219 | 220 | aws_elb = { 221 | productFamily = "Load Balancer" 222 | usagetype = lookup(local.lb_usagetype, var.aws_default_region, "LoadBalancerUsage") 223 | } 224 | 225 | aws_nat_gateway = { 226 | productFamily = "NAT Gateway" 227 | usagetype = lookup(local.nat_gateway_usagetype, var.aws_default_region, "NatGateway-Hours") 228 | } 229 | 230 | aws_redshift_cluster = { 231 | location = var.aws_default_region 232 | productFamily = "Compute Instance" 233 | termType = "OnDemand" 234 | } 235 | 236 | aws_db_instance = { 237 | location = var.aws_default_region 238 | productFamily = "Database Instance" 239 | termType = "OnDemand" 240 | deploymentOption = "Single-AZ" 241 | } 242 | } 243 | 244 | # Map with supported AWS resources 245 | resources_service_code = { 246 | aws_instance = "AmazonEC2" 247 | aws_ebs_volume = "AmazonEC2" 248 | aws_ebs_snapshot = "AmazonEC2" 249 | aws_ebs_snapshot_copy = "AmazonEC2" 250 | aws_ebs_snapshot_copy = "AmazonEC2" 251 | aws_lb = "AmazonEC2" 252 | aws_alb = "AmazonEC2" 253 | aws_elb = "AmazonEC2" 254 | aws_nat_gateway = "AmazonEC2" 255 | aws_redshift_cluster = "AmazonRedshift" 256 | aws_db_instance = "AmazonRDS" 257 | 258 | # Custom types 259 | _aws_ebs_provisioned_iops = "AmazonEC2" 260 | } 261 | 262 | 263 | ################################################# 264 | # NAT Gateway has "usagetype" filter per region 265 | ################################################# 266 | nat_gateway_usagetype = { 267 | us-east-1 = "NatGateway-Hours" # default 268 | us-east-2 = "USE2-NatGateway-Hours" 269 | us-west-1 = "USW1-NatGateway-Hours" 270 | us-west-2 = "USW2-NatGateway-Hours" 271 | af-south-1 = "AFS1-NatGateway-Hours" 272 | ap-east-1 = "APE1-NatGateway-Hours" 273 | ap-south-1 = "APS1-NatGateway-Hours" 274 | ap-northeast-1 = "APN1-NatGateway-Hours" 275 | ap-northeast-2 = "APN2-NatGateway-Hours" 276 | ap-northeast-3 = "APN3-NatGateway-Hours" 277 | ap-southeast-1 = "APS1-NatGateway-Hours" 278 | ap-southeast-2 = "APS2-NatGateway-Hours" 279 | ap-southeast-3 = "APS3-NatGateway-Hours" 280 | ca-central-1 = "CAN1-NatGateway-Hours" 281 | eu-central-1 = "EUC1-NatGateway-Hours" 282 | eu-central-2 = "EUC2-NatGateway-Hours" 283 | eu-west-1 = "EU-NatGateway-Hours" 284 | eu-west-2 = "EUW2-NatGateway-Hours" 285 | eu-west-3 = "EUW3-NatGateway-Hours" 286 | eu-south-1 = "EUS1-NatGateway-Hours" 287 | eu-north-1 = "EUN1-NatGateway-Hours" 288 | me-south-1 = "MES1-NatGateway-Hours" 289 | me-central-1 = "MEC1-NatGateway-Hours" 290 | sa-east-1 = "SAE1-NatGateway-Hours" 291 | us-gov-west-1 = "UGW1-NatGateway-Hours" 292 | us-gov-east-1 = "UGE1-NatGateway-Hours" 293 | } 294 | 295 | ################################################# 296 | # ELB/ALB/NLB has "usagetype" filter per region 297 | ################################################# 298 | lb_usagetype = { 299 | us-east-1 = "LoadBalancerUsage" # default 300 | us-east-2 = "USE2-LoadBalancerUsage" 301 | us-west-1 = "USW1-LoadBalancerUsage" 302 | us-west-2 = "USW2-LoadBalancerUsage" 303 | af-south-1 = "AFS1-LoadBalancerUsage" 304 | ap-east-1 = "APE1-LoadBalancerUsage" 305 | ap-south-1 = "APS1-LoadBalancerUsage" 306 | ap-northeast-1 = "APN1-LoadBalancerUsage" 307 | ap-northeast-2 = "APN2-LoadBalancerUsage" 308 | ap-northeast-3 = "APN3-LoadBalancerUsage" 309 | ap-southeast-1 = "APS1-LoadBalancerUsage" 310 | ap-southeast-2 = "APS2-LoadBalancerUsage" 311 | ap-southeast-3 = "APS3-LoadBalancerUsage" 312 | ca-central-1 = "CAN1-LoadBalancerUsage" 313 | eu-central-1 = "EUC1-LoadBalancerUsage" 314 | eu-central-2 = "EUC2-LoadBalancerUsage" 315 | eu-west-1 = "EU-LoadBalancerUsage" 316 | eu-west-2 = "EUW2-LoadBalancerUsage" 317 | eu-west-3 = "EUW3-LoadBalancerUsage" 318 | eu-south-1 = "EUS1-LoadBalancerUsage" 319 | eu-north-1 = "EUN1-LoadBalancerUsage" 320 | me-south-1 = "MES1-LoadBalancerUsage" 321 | me-central-1 = "MEC1-LoadBalancerUsage" 322 | sa-east-1 = "SAE1-LoadBalancerUsage" 323 | us-gov-west-1 = "UGW1-LoadBalancerUsage" 324 | us-gov-east-1 = "UGE1-LoadBalancerUsage" 325 | } 326 | 327 | ################################################# 328 | # EBS Snapshot has "usagetype" filter per region 329 | ################################################# 330 | ebs_snapshot_usagetype = { 331 | us-east-1 = "EBS:SnapshotUsage" # default 332 | us-east-2 = "USE2-EBS:SnapshotUsage" 333 | us-west-1 = "USW1-EBS:SnapshotUsage" 334 | us-west-2 = "USW2-EBS:SnapshotUsage" 335 | af-south-1 = "AFS1-EBS:SnapshotUsage" 336 | ap-east-1 = "APE1-EBS:SnapshotUsage" 337 | ap-south-1 = "APS1-EBS:SnapshotUsage" 338 | ap-northeast-1 = "APN1-EBS:SnapshotUsage" 339 | ap-northeast-2 = "APN2-EBS:SnapshotUsage" 340 | ap-northeast-3 = "APN3-EBS:SnapshotUsage" 341 | ap-southeast-1 = "APS1-EBS:SnapshotUsage" 342 | ap-southeast-2 = "APS2-EBS:SnapshotUsage" 343 | ap-southeast-3 = "APS3-EBS:SnapshotUsage" 344 | ca-central-1 = "CAN1-EBS:SnapshotUsage" 345 | eu-central-1 = "EUC1-EBS:SnapshotUsage" 346 | eu-central-2 = "EUC2-EBS:SnapshotUsage" 347 | eu-west-1 = "EU-EBS:SnapshotUsage" 348 | eu-west-2 = "EUW2-EBS:SnapshotUsage" 349 | eu-west-3 = "EUW3-EBS:SnapshotUsage" 350 | eu-south-1 = "EUS1-EBS:SnapshotUsage" 351 | eu-north-1 = "EUN1-EBS:SnapshotUsage" 352 | me-south-1 = "MES1-EBS:SnapshotUsage" 353 | me-central-1 = "MEC1-EBS:SnapshotUsage" 354 | sa-east-1 = "SAE1-EBS:SnapshotUsage" 355 | us-gov-west-1 = "UGW1-EBS:SnapshotUsage" 356 | us-gov-east-1 = "UGE1-EBS:SnapshotUsage" 357 | } 358 | 359 | ################ 360 | # RDS DB Engine 361 | # https://docs.aws.amazon.com/cli/latest/reference/rds/describe-db-engine-versions.html#options 362 | # aws pricing get-attribute-values --region us-east-1 --service-code AmazonRDS --attribute-name databaseEngine 363 | ################ 364 | db_instance_engine = { 365 | mysql = "MySQL" 366 | postgres = "PostgreSQL" 367 | oracle-ee = "Oracle" 368 | oracle-se = "Oracle" 369 | oracle-se1 = "Oracle" 370 | oracle-se2 = "Oracle" 371 | mariadb = "MariaDB" 372 | sqlserver-ex = "SQL Server" 373 | sqlserver-ee = "SQL Server" 374 | sqlserver-se = "SQL Server" 375 | sqlserver-web = "SQL Server" 376 | 377 | aurora = "Aurora MySQL" 378 | aurora-mysql = "Aurora MySQL" 379 | aurora-postgresql = "Aurora PostgreSQL" 380 | } 381 | 382 | # aws pricing get-attribute-values --region us-east-1 --service-code AmazonRDS --attribute-name databaseEdition 383 | db_instance_engine_edition = { 384 | oracle-ee = "Enterprise" 385 | oracle-se = "Standard" 386 | oracle-se1 = "Standard One" 387 | oracle-se2 = "Standard Two" 388 | sqlserver-ee = "Enterprise" 389 | sqlserver-ex = "Express" 390 | sqlserver-se = "Standard" 391 | sqlserver-web = "Web" 392 | } 393 | 394 | } 395 | -------------------------------------------------------------------------------- /examples/fixtures/etc/plan-ebs.json: -------------------------------------------------------------------------------- 1 | { 2 | "format_version": "0.1", 3 | "terraform_version": "0.12.24", 4 | "planned_values": { 5 | "root_module": { 6 | "resources": [ 7 | { 8 | "address": "aws_ebs_snapshot.standard", 9 | "mode": "managed", 10 | "type": "aws_ebs_snapshot", 11 | "name": "standard", 12 | "provider_name": "aws", 13 | "schema_version": 0, 14 | "values": { 15 | "description": null, 16 | "tags": null, 17 | "timeouts": null 18 | } 19 | }, 20 | { 21 | "address": "aws_ebs_snapshot_copy.copied", 22 | "mode": "managed", 23 | "type": "aws_ebs_snapshot_copy", 24 | "name": "copied", 25 | "provider_name": "aws", 26 | "schema_version": 0, 27 | "values": { 28 | "description": null, 29 | "encrypted": null, 30 | "kms_key_id": null, 31 | "source_region": "eu-west-1", 32 | "tags": null 33 | } 34 | }, 35 | { 36 | "address": "aws_ebs_volume.volume_io1", 37 | "mode": "managed", 38 | "type": "aws_ebs_volume", 39 | "name": "volume_io1", 40 | "provider_name": "aws", 41 | "schema_version": 0, 42 | "values": { 43 | "availability_zone": "eu-west-1a", 44 | "outpost_arn": null, 45 | "size": 8, 46 | "tags": null, 47 | "iops": 500, 48 | "type": "io1" 49 | } 50 | }, 51 | { 52 | "address": "aws_ebs_volume.volume_standard", 53 | "mode": "managed", 54 | "type": "aws_ebs_volume", 55 | "name": "volume_standard", 56 | "provider_name": "aws", 57 | "schema_version": 0, 58 | "values": { 59 | "availability_zone": "eu-west-1a", 60 | "outpost_arn": null, 61 | "size": 7, 62 | "tags": null, 63 | "type": "standard" 64 | } 65 | }, 66 | { 67 | "address": "aws_instance.this_1", 68 | "mode": "managed", 69 | "type": "aws_instance", 70 | "name": "this_1", 71 | "provider_name": "aws", 72 | "schema_version": 1, 73 | "values": { 74 | "ami": "ami-06ce3edf0cff21f07", 75 | "credit_specification": [], 76 | "disable_api_termination": null, 77 | "ebs_block_device": [ 78 | { 79 | "delete_on_termination": true, 80 | "device_name": "xvfa", 81 | "volume_size": 20, 82 | "volume_type": "sc1" 83 | }, 84 | { 85 | "delete_on_termination": true, 86 | "device_name": "xvfb", 87 | "iops": 2000, 88 | "volume_size": 30, 89 | "volume_type": "io1" 90 | }, 91 | { 92 | "delete_on_termination": true, 93 | "device_name": "xvfs", 94 | "volume_size": 10 95 | } 96 | ], 97 | "ebs_optimized": null, 98 | "get_password_data": false, 99 | "hibernation": null, 100 | "iam_instance_profile": null, 101 | "instance_initiated_shutdown_behavior": null, 102 | "instance_type": "t3.nano", 103 | "monitoring": null, 104 | "root_block_device": [ 105 | { 106 | "delete_on_termination": true, 107 | "iops": 220, 108 | "volume_type": "io1" 109 | } 110 | ], 111 | "source_dest_check": true, 112 | "tags": null, 113 | "timeouts": null, 114 | "user_data": null, 115 | "user_data_base64": null 116 | } 117 | } 118 | ] 119 | } 120 | }, 121 | "resource_changes": [ 122 | { 123 | "address": "aws_ebs_snapshot.standard", 124 | "mode": "managed", 125 | "type": "aws_ebs_snapshot", 126 | "name": "standard", 127 | "provider_name": "aws", 128 | "change": { 129 | "actions": [ 130 | "create" 131 | ], 132 | "before": null, 133 | "after": { 134 | "description": null, 135 | "tags": null, 136 | "timeouts": null 137 | }, 138 | "after_unknown": { 139 | "data_encryption_key_id": true, 140 | "encrypted": true, 141 | "id": true, 142 | "kms_key_id": true, 143 | "owner_alias": true, 144 | "owner_id": true, 145 | "volume_id": true, 146 | "volume_size": true 147 | } 148 | } 149 | }, 150 | { 151 | "address": "aws_ebs_snapshot_copy.copied", 152 | "mode": "managed", 153 | "type": "aws_ebs_snapshot_copy", 154 | "name": "copied", 155 | "provider_name": "aws", 156 | "change": { 157 | "actions": [ 158 | "create" 159 | ], 160 | "before": null, 161 | "after": { 162 | "description": null, 163 | "encrypted": null, 164 | "kms_key_id": null, 165 | "source_region": "eu-west-1", 166 | "tags": null 167 | }, 168 | "after_unknown": { 169 | "data_encryption_key_id": true, 170 | "id": true, 171 | "owner_alias": true, 172 | "owner_id": true, 173 | "source_snapshot_id": true, 174 | "volume_id": true, 175 | "volume_size": true 176 | } 177 | } 178 | }, 179 | { 180 | "address": "aws_ebs_volume.volume_io1", 181 | "mode": "managed", 182 | "type": "aws_ebs_volume", 183 | "name": "volume_io1", 184 | "provider_name": "aws", 185 | "change": { 186 | "actions": [ 187 | "create" 188 | ], 189 | "before": null, 190 | "after": { 191 | "availability_zone": "eu-west-1a", 192 | "outpost_arn": null, 193 | "size": 8, 194 | "tags": null, 195 | "type": "io1" 196 | }, 197 | "after_unknown": { 198 | "arn": true, 199 | "encrypted": true, 200 | "id": true, 201 | "iops": true, 202 | "kms_key_id": true, 203 | "snapshot_id": true 204 | } 205 | } 206 | }, 207 | { 208 | "address": "aws_ebs_volume.volume_standard", 209 | "mode": "managed", 210 | "type": "aws_ebs_volume", 211 | "name": "volume_standard", 212 | "provider_name": "aws", 213 | "change": { 214 | "actions": [ 215 | "create" 216 | ], 217 | "before": null, 218 | "after": { 219 | "availability_zone": "eu-west-1a", 220 | "outpost_arn": null, 221 | "size": 7, 222 | "tags": null, 223 | "type": "standard" 224 | }, 225 | "after_unknown": { 226 | "arn": true, 227 | "encrypted": true, 228 | "id": true, 229 | "iops": true, 230 | "kms_key_id": true, 231 | "snapshot_id": true 232 | } 233 | } 234 | }, 235 | { 236 | "address": "aws_instance.this_1", 237 | "mode": "managed", 238 | "type": "aws_instance", 239 | "name": "this_1", 240 | "provider_name": "aws", 241 | "change": { 242 | "actions": [ 243 | "create" 244 | ], 245 | "before": null, 246 | "after": { 247 | "ami": "ami-06ce3edf0cff21f07", 248 | "credit_specification": [], 249 | "disable_api_termination": null, 250 | "ebs_block_device": [ 251 | { 252 | "delete_on_termination": true, 253 | "device_name": "xvfa", 254 | "volume_size": 20, 255 | "volume_type": "sc1" 256 | }, 257 | { 258 | "delete_on_termination": true, 259 | "device_name": "xvfb", 260 | "iops": 2000, 261 | "volume_size": 30, 262 | "volume_type": "io1" 263 | }, 264 | { 265 | "delete_on_termination": true, 266 | "device_name": "xvfs", 267 | "volume_size": 10 268 | } 269 | ], 270 | "ebs_optimized": null, 271 | "get_password_data": false, 272 | "hibernation": null, 273 | "iam_instance_profile": null, 274 | "instance_initiated_shutdown_behavior": null, 275 | "instance_type": "t3.nano", 276 | "monitoring": null, 277 | "root_block_device": [ 278 | { 279 | "delete_on_termination": true, 280 | "iops": 220, 281 | "volume_type": "io1" 282 | } 283 | ], 284 | "source_dest_check": true, 285 | "tags": null, 286 | "timeouts": null, 287 | "user_data": null, 288 | "user_data_base64": null 289 | }, 290 | "after_unknown": { 291 | "arn": true, 292 | "associate_public_ip_address": true, 293 | "availability_zone": true, 294 | "cpu_core_count": true, 295 | "cpu_threads_per_core": true, 296 | "credit_specification": [], 297 | "ebs_block_device": [ 298 | { 299 | "encrypted": true, 300 | "iops": true, 301 | "kms_key_id": true, 302 | "snapshot_id": true, 303 | "volume_id": true 304 | }, 305 | { 306 | "encrypted": true, 307 | "kms_key_id": true, 308 | "snapshot_id": true, 309 | "volume_id": true 310 | }, 311 | { 312 | "encrypted": true, 313 | "iops": true, 314 | "kms_key_id": true, 315 | "snapshot_id": true, 316 | "volume_id": true, 317 | "volume_type": true 318 | } 319 | ], 320 | "ephemeral_block_device": true, 321 | "host_id": true, 322 | "id": true, 323 | "instance_state": true, 324 | "ipv6_address_count": true, 325 | "ipv6_addresses": true, 326 | "key_name": true, 327 | "metadata_options": true, 328 | "network_interface": true, 329 | "network_interface_id": true, 330 | "outpost_arn": true, 331 | "password_data": true, 332 | "placement_group": true, 333 | "primary_network_interface_id": true, 334 | "private_dns": true, 335 | "private_ip": true, 336 | "public_dns": true, 337 | "public_ip": true, 338 | "root_block_device": [ 339 | { 340 | "device_name": true, 341 | "encrypted": true, 342 | "kms_key_id": true, 343 | "volume_id": true, 344 | "volume_size": true 345 | } 346 | ], 347 | "security_groups": true, 348 | "subnet_id": true, 349 | "tenancy": true, 350 | "volume_tags": true, 351 | "vpc_security_group_ids": true 352 | } 353 | } 354 | } 355 | ], 356 | "configuration": { 357 | "root_module": { 358 | "resources": [ 359 | { 360 | "address": "aws_ebs_snapshot.standard", 361 | "mode": "managed", 362 | "type": "aws_ebs_snapshot", 363 | "name": "standard", 364 | "provider_config_key": "aws", 365 | "expressions": { 366 | "volume_id": { 367 | "references": [ 368 | "aws_ebs_volume.volume_standard" 369 | ] 370 | } 371 | }, 372 | "schema_version": 0 373 | }, 374 | { 375 | "address": "aws_ebs_snapshot_copy.copied", 376 | "mode": "managed", 377 | "type": "aws_ebs_snapshot_copy", 378 | "name": "copied", 379 | "provider_config_key": "aws", 380 | "expressions": { 381 | "source_region": { 382 | "constant_value": "eu-west-1" 383 | }, 384 | "source_snapshot_id": { 385 | "references": [ 386 | "aws_ebs_snapshot.standard" 387 | ] 388 | } 389 | }, 390 | "schema_version": 0 391 | }, 392 | { 393 | "address": "aws_ebs_volume.volume_io1", 394 | "mode": "managed", 395 | "type": "aws_ebs_volume", 396 | "name": "volume_io1", 397 | "provider_config_key": "aws", 398 | "expressions": { 399 | "availability_zone": { 400 | "constant_value": "eu-west-1a" 401 | }, 402 | "size": { 403 | "constant_value": 8 404 | }, 405 | "type": { 406 | "constant_value": "io1" 407 | } 408 | }, 409 | "schema_version": 0 410 | }, 411 | { 412 | "address": "aws_ebs_volume.volume_standard", 413 | "mode": "managed", 414 | "type": "aws_ebs_volume", 415 | "name": "volume_standard", 416 | "provider_config_key": "aws", 417 | "expressions": { 418 | "availability_zone": { 419 | "constant_value": "eu-west-1a" 420 | }, 421 | "size": { 422 | "constant_value": 7 423 | }, 424 | "type": { 425 | "constant_value": "standard" 426 | } 427 | }, 428 | "schema_version": 0 429 | }, 430 | { 431 | "address": "aws_instance.this_1", 432 | "mode": "managed", 433 | "type": "aws_instance", 434 | "name": "this_1", 435 | "provider_config_key": "aws", 436 | "expressions": { 437 | "ami": { 438 | "constant_value": "ami-06ce3edf0cff21f07" 439 | }, 440 | "ebs_block_device": [ 441 | { 442 | "device_name": { 443 | "constant_value": "xvfs" 444 | }, 445 | "volume_size": { 446 | "constant_value": 10 447 | } 448 | }, 449 | { 450 | "device_name": { 451 | "constant_value": "xvfa" 452 | }, 453 | "volume_size": { 454 | "constant_value": 20 455 | }, 456 | "volume_type": { 457 | "constant_value": "sc1" 458 | } 459 | }, 460 | { 461 | "device_name": { 462 | "constant_value": "xvfb" 463 | }, 464 | "iops": { 465 | "constant_value": 2000 466 | }, 467 | "volume_size": { 468 | "constant_value": 30 469 | }, 470 | "volume_type": { 471 | "constant_value": "io1" 472 | } 473 | } 474 | ], 475 | "instance_type": { 476 | "constant_value": "t3.nano" 477 | }, 478 | "root_block_device": [ 479 | { 480 | "iops": { 481 | "constant_value": 220 482 | }, 483 | "volume_type": { 484 | "constant_value": "io1" 485 | } 486 | } 487 | ] 488 | }, 489 | "schema_version": 1 490 | } 491 | ] 492 | } 493 | } 494 | } 495 | -------------------------------------------------------------------------------- /examples/fixtures/etc/plan-lb-and-nat.json: -------------------------------------------------------------------------------- 1 | { 2 | "format_version": "0.1", 3 | "terraform_version": "0.12.24", 4 | "planned_values": { 5 | "root_module": { 6 | "resources": [ 7 | { 8 | "address": "aws_alb.alb2", 9 | "mode": "managed", 10 | "type": "aws_alb", 11 | "name": "alb2", 12 | "provider_name": "aws", 13 | "schema_version": 0, 14 | "values": { 15 | "access_logs": [], 16 | "drop_invalid_header_fields": false, 17 | "enable_cross_zone_load_balancing": null, 18 | "enable_deletion_protection": false, 19 | "enable_http2": true, 20 | "idle_timeout": 60, 21 | "load_balancer_type": "application", 22 | "name_prefix": null, 23 | "tags": null, 24 | "timeouts": null 25 | } 26 | }, 27 | { 28 | "address": "aws_eip.nat", 29 | "mode": "managed", 30 | "type": "aws_eip", 31 | "name": "nat", 32 | "provider_name": "aws", 33 | "schema_version": 0, 34 | "values": { 35 | "associate_with_private_ip": null, 36 | "tags": null, 37 | "timeouts": null, 38 | "vpc": true 39 | } 40 | }, 41 | { 42 | "address": "aws_elb.elb", 43 | "mode": "managed", 44 | "type": "aws_elb", 45 | "name": "elb", 46 | "provider_name": "aws", 47 | "schema_version": 0, 48 | "values": { 49 | "access_logs": [], 50 | "connection_draining": false, 51 | "connection_draining_timeout": 300, 52 | "cross_zone_load_balancing": true, 53 | "idle_timeout": 60, 54 | "listener": [ 55 | { 56 | "instance_port": 80, 57 | "instance_protocol": "HTTP", 58 | "lb_port": 80, 59 | "lb_protocol": "HTTP", 60 | "ssl_certificate_id": "" 61 | } 62 | ], 63 | "name_prefix": null, 64 | "tags": null 65 | } 66 | }, 67 | { 68 | "address": "aws_lb.alb", 69 | "mode": "managed", 70 | "type": "aws_lb", 71 | "name": "alb", 72 | "provider_name": "aws", 73 | "schema_version": 0, 74 | "values": { 75 | "access_logs": [], 76 | "drop_invalid_header_fields": false, 77 | "enable_cross_zone_load_balancing": null, 78 | "enable_deletion_protection": false, 79 | "enable_http2": true, 80 | "idle_timeout": 60, 81 | "load_balancer_type": "application", 82 | "name_prefix": null, 83 | "tags": null, 84 | "timeouts": null 85 | } 86 | }, 87 | { 88 | "address": "aws_lb.nlb", 89 | "mode": "managed", 90 | "type": "aws_lb", 91 | "name": "nlb", 92 | "provider_name": "aws", 93 | "schema_version": 0, 94 | "values": { 95 | "access_logs": [], 96 | "drop_invalid_header_fields": null, 97 | "enable_cross_zone_load_balancing": false, 98 | "enable_deletion_protection": false, 99 | "enable_http2": null, 100 | "idle_timeout": null, 101 | "load_balancer_type": "network", 102 | "name_prefix": null, 103 | "tags": null, 104 | "timeouts": null 105 | } 106 | }, 107 | { 108 | "address": "aws_lb.undefined_type", 109 | "mode": "managed", 110 | "type": "aws_lb", 111 | "name": "undefined_type", 112 | "provider_name": "aws", 113 | "schema_version": 0, 114 | "values": { 115 | "access_logs": [], 116 | "drop_invalid_header_fields": false, 117 | "enable_cross_zone_load_balancing": null, 118 | "enable_deletion_protection": false, 119 | "enable_http2": true, 120 | "idle_timeout": 60, 121 | "load_balancer_type": "application", 122 | "name_prefix": null, 123 | "tags": null, 124 | "timeouts": null 125 | } 126 | }, 127 | { 128 | "address": "aws_nat_gateway.nat", 129 | "mode": "managed", 130 | "type": "aws_nat_gateway", 131 | "name": "nat", 132 | "provider_name": "aws", 133 | "schema_version": 0, 134 | "values": { 135 | "subnet_id": "subnet-816551c9", 136 | "tags": null 137 | } 138 | } 139 | ] 140 | } 141 | }, 142 | "resource_changes": [ 143 | { 144 | "address": "aws_alb.alb2", 145 | "mode": "managed", 146 | "type": "aws_alb", 147 | "name": "alb2", 148 | "provider_name": "aws", 149 | "change": { 150 | "actions": [ 151 | "create" 152 | ], 153 | "before": null, 154 | "after": { 155 | "access_logs": [], 156 | "drop_invalid_header_fields": false, 157 | "enable_cross_zone_load_balancing": null, 158 | "enable_deletion_protection": false, 159 | "enable_http2": true, 160 | "idle_timeout": 60, 161 | "load_balancer_type": "application", 162 | "name_prefix": null, 163 | "tags": null, 164 | "timeouts": null 165 | }, 166 | "after_unknown": { 167 | "access_logs": [], 168 | "arn": true, 169 | "arn_suffix": true, 170 | "dns_name": true, 171 | "id": true, 172 | "internal": true, 173 | "ip_address_type": true, 174 | "name": true, 175 | "security_groups": true, 176 | "subnet_mapping": true, 177 | "subnets": true, 178 | "vpc_id": true, 179 | "zone_id": true 180 | } 181 | } 182 | }, 183 | { 184 | "address": "aws_eip.nat", 185 | "mode": "managed", 186 | "type": "aws_eip", 187 | "name": "nat", 188 | "provider_name": "aws", 189 | "change": { 190 | "actions": [ 191 | "create" 192 | ], 193 | "before": null, 194 | "after": { 195 | "associate_with_private_ip": null, 196 | "tags": null, 197 | "timeouts": null, 198 | "vpc": true 199 | }, 200 | "after_unknown": { 201 | "allocation_id": true, 202 | "association_id": true, 203 | "domain": true, 204 | "id": true, 205 | "instance": true, 206 | "network_interface": true, 207 | "private_dns": true, 208 | "private_ip": true, 209 | "public_dns": true, 210 | "public_ip": true, 211 | "public_ipv4_pool": true 212 | } 213 | } 214 | }, 215 | { 216 | "address": "aws_elb.elb", 217 | "mode": "managed", 218 | "type": "aws_elb", 219 | "name": "elb", 220 | "provider_name": "aws", 221 | "change": { 222 | "actions": [ 223 | "create" 224 | ], 225 | "before": null, 226 | "after": { 227 | "access_logs": [], 228 | "connection_draining": false, 229 | "connection_draining_timeout": 300, 230 | "cross_zone_load_balancing": true, 231 | "idle_timeout": 60, 232 | "listener": [ 233 | { 234 | "instance_port": 80, 235 | "instance_protocol": "HTTP", 236 | "lb_port": 80, 237 | "lb_protocol": "HTTP", 238 | "ssl_certificate_id": "" 239 | } 240 | ], 241 | "name_prefix": null, 242 | "tags": null 243 | }, 244 | "after_unknown": { 245 | "access_logs": [], 246 | "arn": true, 247 | "availability_zones": true, 248 | "dns_name": true, 249 | "health_check": true, 250 | "id": true, 251 | "instances": true, 252 | "internal": true, 253 | "listener": [ 254 | {} 255 | ], 256 | "name": true, 257 | "security_groups": true, 258 | "source_security_group": true, 259 | "source_security_group_id": true, 260 | "subnets": true, 261 | "zone_id": true 262 | } 263 | } 264 | }, 265 | { 266 | "address": "aws_lb.alb", 267 | "mode": "managed", 268 | "type": "aws_lb", 269 | "name": "alb", 270 | "provider_name": "aws", 271 | "change": { 272 | "actions": [ 273 | "create" 274 | ], 275 | "before": null, 276 | "after": { 277 | "access_logs": [], 278 | "drop_invalid_header_fields": false, 279 | "enable_cross_zone_load_balancing": null, 280 | "enable_deletion_protection": false, 281 | "enable_http2": true, 282 | "idle_timeout": 60, 283 | "load_balancer_type": "application", 284 | "name_prefix": null, 285 | "tags": null, 286 | "timeouts": null 287 | }, 288 | "after_unknown": { 289 | "access_logs": [], 290 | "arn": true, 291 | "arn_suffix": true, 292 | "dns_name": true, 293 | "id": true, 294 | "internal": true, 295 | "ip_address_type": true, 296 | "name": true, 297 | "security_groups": true, 298 | "subnet_mapping": true, 299 | "subnets": true, 300 | "vpc_id": true, 301 | "zone_id": true 302 | } 303 | } 304 | }, 305 | { 306 | "address": "aws_lb.nlb", 307 | "mode": "managed", 308 | "type": "aws_lb", 309 | "name": "nlb", 310 | "provider_name": "aws", 311 | "change": { 312 | "actions": [ 313 | "create" 314 | ], 315 | "before": null, 316 | "after": { 317 | "access_logs": [], 318 | "drop_invalid_header_fields": null, 319 | "enable_cross_zone_load_balancing": false, 320 | "enable_deletion_protection": false, 321 | "enable_http2": null, 322 | "idle_timeout": null, 323 | "load_balancer_type": "network", 324 | "name_prefix": null, 325 | "tags": null, 326 | "timeouts": null 327 | }, 328 | "after_unknown": { 329 | "access_logs": [], 330 | "arn": true, 331 | "arn_suffix": true, 332 | "dns_name": true, 333 | "id": true, 334 | "internal": true, 335 | "ip_address_type": true, 336 | "name": true, 337 | "security_groups": true, 338 | "subnet_mapping": true, 339 | "subnets": true, 340 | "vpc_id": true, 341 | "zone_id": true 342 | } 343 | } 344 | }, 345 | { 346 | "address": "aws_lb.undefined_type", 347 | "mode": "managed", 348 | "type": "aws_lb", 349 | "name": "undefined_type", 350 | "provider_name": "aws", 351 | "change": { 352 | "actions": [ 353 | "create" 354 | ], 355 | "before": null, 356 | "after": { 357 | "access_logs": [], 358 | "drop_invalid_header_fields": false, 359 | "enable_cross_zone_load_balancing": null, 360 | "enable_deletion_protection": false, 361 | "enable_http2": true, 362 | "idle_timeout": 60, 363 | "load_balancer_type": "application", 364 | "name_prefix": null, 365 | "tags": null, 366 | "timeouts": null 367 | }, 368 | "after_unknown": { 369 | "access_logs": [], 370 | "arn": true, 371 | "arn_suffix": true, 372 | "dns_name": true, 373 | "id": true, 374 | "internal": true, 375 | "ip_address_type": true, 376 | "name": true, 377 | "security_groups": true, 378 | "subnet_mapping": true, 379 | "subnets": true, 380 | "vpc_id": true, 381 | "zone_id": true 382 | } 383 | } 384 | }, 385 | { 386 | "address": "aws_nat_gateway.nat", 387 | "mode": "managed", 388 | "type": "aws_nat_gateway", 389 | "name": "nat", 390 | "provider_name": "aws", 391 | "change": { 392 | "actions": [ 393 | "create" 394 | ], 395 | "before": null, 396 | "after": { 397 | "subnet_id": "subnet-816551c9", 398 | "tags": null 399 | }, 400 | "after_unknown": { 401 | "allocation_id": true, 402 | "id": true, 403 | "network_interface_id": true, 404 | "private_ip": true, 405 | "public_ip": true 406 | } 407 | } 408 | } 409 | ], 410 | "prior_state": { 411 | "format_version": "0.1", 412 | "terraform_version": "0.12.24", 413 | "values": { 414 | "root_module": { 415 | "resources": [ 416 | { 417 | "address": "data.aws_subnet_ids.all", 418 | "mode": "data", 419 | "type": "aws_subnet_ids", 420 | "name": "all", 421 | "provider_name": "aws", 422 | "schema_version": 0, 423 | "values": { 424 | "filter": null, 425 | "id": "vpc-4b42aa32", 426 | "ids": [ 427 | "subnet-816551c9", 428 | "subnet-8f2442d5", 429 | "subnet-e3f0d585" 430 | ], 431 | "tags": null, 432 | "vpc_id": "vpc-4b42aa32" 433 | } 434 | }, 435 | { 436 | "address": "data.aws_vpc.default", 437 | "mode": "data", 438 | "type": "aws_vpc", 439 | "name": "default", 440 | "provider_name": "aws", 441 | "schema_version": 0, 442 | "values": { 443 | "arn": "arn:aws:ec2:eu-west-1:052235879155:vpc/vpc-4b42aa32", 444 | "cidr_block": "172.31.0.0/16", 445 | "cidr_block_associations": [ 446 | { 447 | "association_id": "vpc-cidr-assoc-d7fdedbc", 448 | "cidr_block": "172.31.0.0/16", 449 | "state": "associated" 450 | } 451 | ], 452 | "default": true, 453 | "dhcp_options_id": "dopt-f80e549e", 454 | "enable_dns_hostnames": true, 455 | "enable_dns_support": true, 456 | "filter": null, 457 | "id": "vpc-4b42aa32", 458 | "instance_tenancy": "default", 459 | "ipv6_association_id": null, 460 | "ipv6_cidr_block": null, 461 | "main_route_table_id": "rtb-a73c2ede", 462 | "owner_id": "052235879155", 463 | "state": "available", 464 | "tags": {} 465 | } 466 | } 467 | ] 468 | } 469 | } 470 | }, 471 | "configuration": { 472 | "root_module": { 473 | "resources": [ 474 | { 475 | "address": "aws_alb.alb2", 476 | "mode": "managed", 477 | "type": "aws_alb", 478 | "name": "alb2", 479 | "provider_config_key": "aws", 480 | "expressions": { 481 | "load_balancer_type": { 482 | "constant_value": "application" 483 | } 484 | }, 485 | "schema_version": 0 486 | }, 487 | { 488 | "address": "aws_eip.nat", 489 | "mode": "managed", 490 | "type": "aws_eip", 491 | "name": "nat", 492 | "provider_config_key": "aws", 493 | "expressions": { 494 | "vpc": { 495 | "constant_value": true 496 | } 497 | }, 498 | "schema_version": 0 499 | }, 500 | { 501 | "address": "aws_elb.elb", 502 | "mode": "managed", 503 | "type": "aws_elb", 504 | "name": "elb", 505 | "provider_config_key": "aws", 506 | "expressions": { 507 | "listener": [ 508 | { 509 | "instance_port": { 510 | "constant_value": 80 511 | }, 512 | "instance_protocol": { 513 | "constant_value": "HTTP" 514 | }, 515 | "lb_port": { 516 | "constant_value": 80 517 | }, 518 | "lb_protocol": { 519 | "constant_value": "HTTP" 520 | } 521 | } 522 | ] 523 | }, 524 | "schema_version": 0 525 | }, 526 | { 527 | "address": "aws_lb.alb", 528 | "mode": "managed", 529 | "type": "aws_lb", 530 | "name": "alb", 531 | "provider_config_key": "aws", 532 | "expressions": { 533 | "load_balancer_type": { 534 | "constant_value": "application" 535 | } 536 | }, 537 | "schema_version": 0 538 | }, 539 | { 540 | "address": "aws_lb.nlb", 541 | "mode": "managed", 542 | "type": "aws_lb", 543 | "name": "nlb", 544 | "provider_config_key": "aws", 545 | "expressions": { 546 | "load_balancer_type": { 547 | "constant_value": "network" 548 | } 549 | }, 550 | "schema_version": 0 551 | }, 552 | { 553 | "address": "aws_lb.undefined_type", 554 | "mode": "managed", 555 | "type": "aws_lb", 556 | "name": "undefined_type", 557 | "provider_config_key": "aws", 558 | "schema_version": 0 559 | }, 560 | { 561 | "address": "aws_nat_gateway.nat", 562 | "mode": "managed", 563 | "type": "aws_nat_gateway", 564 | "name": "nat", 565 | "provider_config_key": "aws", 566 | "expressions": { 567 | "allocation_id": { 568 | "references": [ 569 | "aws_eip.nat" 570 | ] 571 | }, 572 | "subnet_id": { 573 | "references": [ 574 | "data.aws_subnet_ids.all" 575 | ] 576 | } 577 | }, 578 | "schema_version": 0 579 | }, 580 | { 581 | "address": "data.aws_subnet_ids.all", 582 | "mode": "data", 583 | "type": "aws_subnet_ids", 584 | "name": "all", 585 | "provider_config_key": "aws", 586 | "expressions": { 587 | "vpc_id": { 588 | "references": [ 589 | "data.aws_vpc.default" 590 | ] 591 | } 592 | }, 593 | "schema_version": 0 594 | }, 595 | { 596 | "address": "data.aws_vpc.default", 597 | "mode": "data", 598 | "type": "aws_vpc", 599 | "name": "default", 600 | "provider_config_key": "aws", 601 | "expressions": { 602 | "default": { 603 | "constant_value": true 604 | } 605 | }, 606 | "schema_version": 0 607 | } 608 | ] 609 | } 610 | } 611 | } 612 | -------------------------------------------------------------------------------- /dev/aws_pricing.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | aws pricing describe-services --format-version aws_v1 --region us-east-1 4 | 5 | ```json 6 | { 7 | "Services": [ 8 | { 9 | "ServiceCode": "AmazonEC2", 10 | "AttributeNames": [ 11 | "instanceCapacityMetal", 12 | "volumeType", 13 | "maxIopsvolume", 14 | "instance", 15 | "instanceCapacity10xlarge", 16 | "locationType", 17 | "toLocationType", 18 | "instanceFamily", 19 | "operatingSystem", 20 | "clockSpeed", 21 | "LeaseContractLength", 22 | "ecu", 23 | "networkPerformance", 24 | "instanceCapacity8xlarge", 25 | "group", 26 | "maxThroughputvolume", 27 | "gpuMemory", 28 | "ebsOptimized", 29 | "maxVolumeSize", 30 | "gpu", 31 | "intelAvxAvailable", 32 | "processorFeatures", 33 | "instanceCapacity4xlarge", 34 | "servicecode", 35 | "groupDescription", 36 | "elasticGraphicsType", 37 | "volumeApiName", 38 | "processorArchitecture", 39 | "fromLocation", 40 | "physicalCores", 41 | "productFamily", 42 | "fromLocationType", 43 | "enhancedNetworkingSupported", 44 | "intelTurboAvailable", 45 | "memory", 46 | "dedicatedEbsThroughput", 47 | "vcpu", 48 | "OfferingClass", 49 | "instanceCapacityLarge", 50 | "capacitystatus", 51 | "termType", 52 | "storage", 53 | "toLocation", 54 | "intelAvx2Available", 55 | "storageMedia", 56 | "physicalProcessor", 57 | "provisioned", 58 | "servicename", 59 | "PurchaseOption", 60 | "instancesku", 61 | "productType", 62 | "instanceCapacity18xlarge", 63 | "instanceType", 64 | "tenancy", 65 | "usagetype", 66 | "normalizationSizeFactor", 67 | "instanceCapacity2xlarge", 68 | "instanceCapacity16xlarge", 69 | "maxIopsBurstPerformance", 70 | "instanceCapacity12xlarge", 71 | "instanceCapacity32xlarge", 72 | "instanceCapacityXlarge", 73 | "licenseModel", 74 | "currentGeneration", 75 | "preInstalledSw", 76 | "transferType", 77 | "location", 78 | "instanceCapacity24xlarge", 79 | "instanceCapacity9xlarge", 80 | "instanceCapacityMedium", 81 | "operation", 82 | "resourceType" 83 | ] 84 | }, 85 | { 86 | "ServiceCode": "AmazonRDS", 87 | "AttributeNames": [ 88 | "productFamily", 89 | "volumeType", 90 | "engineCode", 91 | "enhancedNetworkingSupported", 92 | "memory", 93 | "dedicatedEbsThroughput", 94 | "vcpu", 95 | "OfferingClass", 96 | "termType", 97 | "locationType", 98 | "storage", 99 | "instanceFamily", 100 | "storageMedia", 101 | "databaseEdition", 102 | "physicalProcessor", 103 | "LeaseContractLength", 104 | "clockSpeed", 105 | "networkPerformance", 106 | "deploymentOption", 107 | "servicename", 108 | "PurchaseOption", 109 | "group", 110 | "minVolumeSize", 111 | "instanceTypeFamily", 112 | "instanceType", 113 | "usagetype", 114 | "normalizationSizeFactor", 115 | "maxVolumeSize", 116 | "databaseEngine", 117 | "processorFeatures", 118 | "Restriction", 119 | "servicecode", 120 | "groupDescription", 121 | "licenseModel", 122 | "currentGeneration", 123 | "location", 124 | "processorArchitecture", 125 | "operation" 126 | ] 127 | }, 128 | { 129 | "ServiceCode": "AmazonRedshift", 130 | "AttributeNames": [ 131 | "productFamily", 132 | "memory", 133 | "vcpu", 134 | "OfferingClass", 135 | "termType", 136 | "locationType", 137 | "description", 138 | "storage", 139 | "storageMedia", 140 | "LeaseContractLength", 141 | "ecu", 142 | "servicename", 143 | "PurchaseOption", 144 | "group", 145 | "concurrencyscalingfreeusage", 146 | "io", 147 | "instanceType", 148 | "usagetype", 149 | "Restriction", 150 | "servicecode", 151 | "groupDescription", 152 | "pricingUnit", 153 | "currentGeneration", 154 | "usageFamily", 155 | "location", 156 | "operation" 157 | ] 158 | } 159 | ], 160 | "FormatVersion": "aws_v1" 161 | } 162 | ``` 163 | #### 164 | 165 | RDS 166 | 167 | aws pricing get-products --region us-east-1 --format-version aws_v1 --service-code AmazonRDS --filters Type=TERM_MATCH,Field=instanceType,Value="db.t3.large" Type=TERM_MATCH,Field=location,Value="US East (N. Virginia)" Type=TERM_MATCH,Field=productFamily,Value="Database Instance" Type=TERM_MATCH,Field=termType,Value="OnDemand" Type=TERM_MATCH,Field=databaseEngine,Value="PostgreSQL" Type=TERM_MATCH,Field=deploymentOption,Value="Single-AZ" 168 | 169 | aws pricing get-attribute-values --region us-east-1 --service-code AmazonRDS --attribute-name instanceType 170 | 171 | aws pricing get-attribute-values --region us-east-1 --service-code AmazonRDS --attribute-name databaseEngine 172 | ```{ 173 | "AttributeValues": [ 174 | { 175 | "Value": "Any" 176 | }, 177 | { 178 | "Value": "Aurora MySQL" 179 | }, 180 | { 181 | "Value": "Aurora PostgreSQL" 182 | }, 183 | { 184 | "Value": "MariaDB" 185 | }, 186 | { 187 | "Value": "MySQL (on-premise for Outpost)" 188 | }, 189 | { 190 | "Value": "MySQL" 191 | }, 192 | { 193 | "Value": "Oracle" 194 | }, 195 | { 196 | "Value": "PostgreSQL (on-premise for Outpost)" 197 | }, 198 | { 199 | "Value": "PostgreSQL (on-premise for Outposts)" 200 | }, 201 | { 202 | "Value": "PostgreSQL" 203 | }, 204 | { 205 | "Value": "SQL Server" 206 | } 207 | ] 208 | } 209 | ``` 210 | 211 | aws pricing get-attribute-values --region us-east-1 --service-code AmazonRDS --attribute-name licenseModel 212 | ``` 213 | { 214 | "AttributeValues": [ 215 | { 216 | "Value": "Bring your own license" 217 | }, 218 | { 219 | "Value": "License included" 220 | }, 221 | { 222 | "Value": "No license required" 223 | }, 224 | { 225 | "Value": "On-premises customer provided license" 226 | } 227 | ] 228 | } 229 | ``` 230 | 231 | 232 | aws pricing get-attribute-values --region us-east-1 --service-code AmazonRDS --attribute-name volumeType 233 | ``` 234 | { 235 | "AttributeValues": [ 236 | { 237 | "Value": "General Purpose-Aurora" 238 | }, 239 | { 240 | "Value": "General Purpose" 241 | }, 242 | { 243 | "Value": "Magnetic" 244 | }, 245 | { 246 | "Value": "Provisioned IOPS" 247 | } 248 | ] 249 | } 250 | ``` 251 | 252 | aws pricing get-attribute-values --region us-east-1 --service-code AmazonRDS --attribute-name deploymentOption 253 | { 254 | "AttributeValues": [ 255 | { 256 | "Value": "Multi-AZ (SQL Server Mirror)" 257 | }, 258 | { 259 | "Value": "Multi-AZ" 260 | }, 261 | { 262 | "Value": "Single-AZ" 263 | } 264 | ] 265 | } 266 | 267 | 268 | #### 269 | 270 | aws pricing get-products --region us-east-1 --format-version aws_v1 --service-code AmazonEC2 --filters Type=TERM_MATCH,Field=capacitystatus,Value="Used" Type=TERM_MATCH,Field=instanceType,Value="t2.micro" Type=TERM_MATCH,Field=licenseModel,Value="No License required" Type=TERM_MATCH,Field=location,Value="EU (Ireland)" Type=TERM_MATCH,Field=operatingSystem,Value="Linux" Type=TERM_MATCH,Field=preInstalledSw,Value="NA" Type=TERM_MATCH,Field=tenancy,Value="Shared" 271 | 272 | 273 | Response: 274 | { 275 | "PriceList": [ 276 | "{\"product\":{\"productFamily\":\"Compute Instance\",\"attributes\":{\"enhancedNetworkingSupported\":\"No\",\"intelTurboAvailable\":\"Yes\",\"memory\":\"1 GiB\",\"vcpu\":\"1\",\"capacitystatus\":\"Used\",\"locationType\":\"AWS Region\",\"storage\":\"EBS only\",\"instanceFamily\":\"General purpose\",\"operatingSystem\":\"Linux\",\"intelAvx2Available\":\"No\",\"physicalProcessor\":\"Intel Xeon Family\",\"clockSpeed\":\"Up to 3.3 GHz\",\"ecu\":\"Variable\",\"networkPerformance\":\"Low to Moderate\",\"servicename\":\"Amazon Elastic Compute Cloud\",\"instanceType\":\"t2.micro\",\"tenancy\":\"Shared\",\"usagetype\":\"EU-BoxUsage:t2.micro\",\"normalizationSizeFactor\":\"0.5\",\"intelAvxAvailable\":\"Yes\",\"processorFeatures\":\"Intel AVX; Intel Turbo\",\"servicecode\":\"AmazonEC2\",\"licenseModel\":\"No License required\",\"currentGeneration\":\"Yes\",\"preInstalledSw\":\"NA\",\"location\":\"EU (Ireland)\",\"processorArchitecture\":\"32-bit or 64-bit\",\"operation\":\"RunInstances\"},\"sku\":\"STTHYT3WDDQU8UBR\"},\"serviceCode\":\"AmazonEC2\",\"terms\":{\"OnDemand\":{\"STTHYT3WDDQU8UBR.JRTCKXETXF\":{\"priceDimensions\":{\"STTHYT3WDDQU8UBR.JRTCKXETXF.6YS6EN2CT7\":{\"unit\":\"Hrs\",\"endRange\":\"Inf\",\"description\":\"$0.0126 per On Demand Linux t2.micro Instance Hour\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.JRTCKXETXF.6YS6EN2CT7\",\"beginRange\":\"0\",\"pricePerUnit\":{\"USD\":\"0.0126000000\"}}},\"sku\":\"STTHYT3WDDQU8UBR\",\"effectiveDate\":\"2021-01-01T00:00:00Z\",\"offerTermCode\":\"JRTCKXETXF\",\"termAttributes\":{}}},\"Reserved\":{\"STTHYT3WDDQU8UBR.CUZHX8X6JH\":{\"priceDimensions\":{\"STTHYT3WDDQU8UBR.CUZHX8X6JH.2TG2D8R56U\":{\"unit\":\"Quantity\",\"description\":\"Upfront Fee\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.CUZHX8X6JH.2TG2D8R56U\",\"pricePerUnit\":{\"USD\":\"43\"}},\"STTHYT3WDDQU8UBR.CUZHX8X6JH.6YS6EN2CT7\":{\"unit\":\"Hrs\",\"endRange\":\"Inf\",\"description\":\"Linux/UNIX (Amazon VPC), t2.micro reserved instance applied\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.CUZHX8X6JH.6YS6EN2CT7\",\"beginRange\":\"0\",\"pricePerUnit\":{\"USD\":\"0.0049000000\"}}},\"sku\":\"STTHYT3WDDQU8UBR\",\"effectiveDate\":\"2017-10-31T23:59:59Z\",\"offerTermCode\":\"CUZHX8X6JH\",\"termAttributes\":{\"LeaseContractLength\":\"1yr\",\"OfferingClass\":\"convertible\",\"PurchaseOption\":\"Partial Upfront\"}},\"STTHYT3WDDQU8UBR.7NE97W5U4E\":{\"priceDimensions\":{\"STTHYT3WDDQU8UBR.7NE97W5U4E.6YS6EN2CT7\":{\"unit\":\"Hrs\",\"endRange\":\"Inf\",\"description\":\"Linux/UNIX (Amazon VPC), t2.micro reserved instance applied\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.7NE97W5U4E.6YS6EN2CT7\",\"beginRange\":\"0\",\"pricePerUnit\":{\"USD\":\"0.0102000000\"}}},\"sku\":\"STTHYT3WDDQU8UBR\",\"effectiveDate\":\"2017-10-31T23:59:59Z\",\"offerTermCode\":\"7NE97W5U4E\",\"termAttributes\":{\"LeaseContractLength\":\"1yr\",\"OfferingClass\":\"convertible\",\"PurchaseOption\":\"No Upfront\"}},\"STTHYT3WDDQU8UBR.6QCMYABX3D\":{\"priceDimensions\":{\"STTHYT3WDDQU8UBR.6QCMYABX3D.2TG2D8R56U\":{\"unit\":\"Quantity\",\"description\":\"Upfront Fee\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.6QCMYABX3D.2TG2D8R56U\",\"pricePerUnit\":{\"USD\":\"72\"}},\"STTHYT3WDDQU8UBR.6QCMYABX3D.6YS6EN2CT7\":{\"unit\":\"Hrs\",\"endRange\":\"Inf\",\"description\":\"Linux/UNIX (Amazon VPC), t2.micro reserved instance applied\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.6QCMYABX3D.6YS6EN2CT7\",\"beginRange\":\"0\",\"pricePerUnit\":{\"USD\":\"0.0000000000\"}}},\"sku\":\"STTHYT3WDDQU8UBR\",\"effectiveDate\":\"2017-08-31T23:59:59Z\",\"offerTermCode\":\"6QCMYABX3D\",\"termAttributes\":{\"LeaseContractLength\":\"1yr\",\"OfferingClass\":\"standard\",\"PurchaseOption\":\"All Upfront\"}},\"STTHYT3WDDQU8UBR.38NPMPTW36\":{\"priceDimensions\":{\"STTHYT3WDDQU8UBR.38NPMPTW36.2TG2D8R56U\":{\"unit\":\"Quantity\",\"description\":\"Upfront Fee\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.38NPMPTW36.2TG2D8R56U\",\"pricePerUnit\":{\"USD\":\"77\"}},\"STTHYT3WDDQU8UBR.38NPMPTW36.6YS6EN2CT7\":{\"unit\":\"Hrs\",\"endRange\":\"Inf\",\"description\":\"Linux/UNIX (Amazon VPC), t2.micro reserved instance applied\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.38NPMPTW36.6YS6EN2CT7\",\"beginRange\":\"0\",\"pricePerUnit\":{\"USD\":\"0.0029000000\"}}},\"sku\":\"STTHYT3WDDQU8UBR\",\"effectiveDate\":\"2017-08-31T23:59:59Z\",\"offerTermCode\":\"38NPMPTW36\",\"termAttributes\":{\"LeaseContractLength\":\"3yr\",\"OfferingClass\":\"standard\",\"PurchaseOption\":\"Partial Upfront\"}},\"STTHYT3WDDQU8UBR.Z2E3P23VKM\":{\"priceDimensions\":{\"STTHYT3WDDQU8UBR.Z2E3P23VKM.6YS6EN2CT7\":{\"unit\":\"Hrs\",\"endRange\":\"Inf\",\"description\":\"Linux/UNIX (Amazon VPC), t2.micro reserved instance applied\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.Z2E3P23VKM.6YS6EN2CT7\",\"beginRange\":\"0\",\"pricePerUnit\":{\"USD\":\"0.0073000000\"}}},\"sku\":\"STTHYT3WDDQU8UBR\",\"effectiveDate\":\"2017-08-31T23:59:59Z\",\"offerTermCode\":\"Z2E3P23VKM\",\"termAttributes\":{\"LeaseContractLength\":\"3yr\",\"OfferingClass\":\"convertible\",\"PurchaseOption\":\"No Upfront\"}},\"STTHYT3WDDQU8UBR.NQ3QZPMQV9\":{\"priceDimensions\":{\"STTHYT3WDDQU8UBR.NQ3QZPMQV9.2TG2D8R56U\":{\"unit\":\"Quantity\",\"description\":\"Upfront Fee\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.NQ3QZPMQV9.2TG2D8R56U\",\"pricePerUnit\":{\"USD\":\"145\"}},\"STTHYT3WDDQU8UBR.NQ3QZPMQV9.6YS6EN2CT7\":{\"unit\":\"Hrs\",\"endRange\":\"Inf\",\"description\":\"Linux/UNIX (Amazon VPC), t2.micro reserved instance applied\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.NQ3QZPMQV9.6YS6EN2CT7\",\"beginRange\":\"0\",\"pricePerUnit\":{\"USD\":\"0.0000000000\"}}},\"sku\":\"STTHYT3WDDQU8UBR\",\"effectiveDate\":\"2017-08-31T23:59:59Z\",\"offerTermCode\":\"NQ3QZPMQV9\",\"termAttributes\":{\"LeaseContractLength\":\"3yr\",\"OfferingClass\":\"standard\",\"PurchaseOption\":\"All Upfront\"}},\"STTHYT3WDDQU8UBR.BPH4J8HBKS\":{\"priceDimensions\":{\"STTHYT3WDDQU8UBR.BPH4J8HBKS.6YS6EN2CT7\":{\"unit\":\"Hrs\",\"endRange\":\"Inf\",\"description\":\"Linux/UNIX (Amazon VPC), t2.micro reserved instance applied\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.BPH4J8HBKS.6YS6EN2CT7\",\"beginRange\":\"0\",\"pricePerUnit\":{\"USD\":\"0.0063000000\"}}},\"sku\":\"STTHYT3WDDQU8UBR\",\"effectiveDate\":\"2017-08-31T23:59:59Z\",\"offerTermCode\":\"BPH4J8HBKS\",\"termAttributes\":{\"LeaseContractLength\":\"3yr\",\"OfferingClass\":\"standard\",\"PurchaseOption\":\"No Upfront\"}},\"STTHYT3WDDQU8UBR.R5XV2EPZQZ\":{\"priceDimensions\":{\"STTHYT3WDDQU8UBR.R5XV2EPZQZ.2TG2D8R56U\":{\"unit\":\"Quantity\",\"description\":\"Upfront Fee\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.R5XV2EPZQZ.2TG2D8R56U\",\"pricePerUnit\":{\"USD\":\"89\"}},\"STTHYT3WDDQU8UBR.R5XV2EPZQZ.6YS6EN2CT7\":{\"unit\":\"Hrs\",\"endRange\":\"Inf\",\"description\":\"Linux/UNIX (Amazon VPC), t2.micro reserved instance applied\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.R5XV2EPZQZ.6YS6EN2CT7\",\"beginRange\":\"0\",\"pricePerUnit\":{\"USD\":\"0.0034000000\"}}},\"sku\":\"STTHYT3WDDQU8UBR\",\"effectiveDate\":\"2017-08-31T23:59:59Z\",\"offerTermCode\":\"R5XV2EPZQZ\",\"termAttributes\":{\"LeaseContractLength\":\"3yr\",\"OfferingClass\":\"convertible\",\"PurchaseOption\":\"Partial Upfront\"}},\"STTHYT3WDDQU8UBR.HU7G6KETJZ\":{\"priceDimensions\":{\"STTHYT3WDDQU8UBR.HU7G6KETJZ.2TG2D8R56U\":{\"unit\":\"Quantity\",\"description\":\"Upfront Fee\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.HU7G6KETJZ.2TG2D8R56U\",\"pricePerUnit\":{\"USD\":\"37\"}},\"STTHYT3WDDQU8UBR.HU7G6KETJZ.6YS6EN2CT7\":{\"unit\":\"Hrs\",\"endRange\":\"Inf\",\"description\":\"Linux/UNIX (Amazon VPC), t2.micro reserved instance applied\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.HU7G6KETJZ.6YS6EN2CT7\",\"beginRange\":\"0\",\"pricePerUnit\":{\"USD\":\"0.0042000000\"}}},\"sku\":\"STTHYT3WDDQU8UBR\",\"effectiveDate\":\"2017-08-31T23:59:59Z\",\"offerTermCode\":\"HU7G6KETJZ\",\"termAttributes\":{\"LeaseContractLength\":\"1yr\",\"OfferingClass\":\"standard\",\"PurchaseOption\":\"Partial Upfront\"}},\"STTHYT3WDDQU8UBR.4NA7Y494T4\":{\"priceDimensions\":{\"STTHYT3WDDQU8UBR.4NA7Y494T4.6YS6EN2CT7\":{\"unit\":\"Hrs\",\"endRange\":\"Inf\",\"description\":\"Linux/UNIX (Amazon VPC), t2.micro reserved instance applied\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.4NA7Y494T4.6YS6EN2CT7\",\"beginRange\":\"0\",\"pricePerUnit\":{\"USD\":\"0.0089000000\"}}},\"sku\":\"STTHYT3WDDQU8UBR\",\"effectiveDate\":\"2017-08-31T23:59:59Z\",\"offerTermCode\":\"4NA7Y494T4\",\"termAttributes\":{\"LeaseContractLength\":\"1yr\",\"OfferingClass\":\"standard\",\"PurchaseOption\":\"No Upfront\"}},\"STTHYT3WDDQU8UBR.MZU6U2429S\":{\"priceDimensions\":{\"STTHYT3WDDQU8UBR.MZU6U2429S.2TG2D8R56U\":{\"unit\":\"Quantity\",\"description\":\"Upfront Fee\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.MZU6U2429S.2TG2D8R56U\",\"pricePerUnit\":{\"USD\":\"174\"}},\"STTHYT3WDDQU8UBR.MZU6U2429S.6YS6EN2CT7\":{\"unit\":\"Hrs\",\"endRange\":\"Inf\",\"description\":\"Linux/UNIX (Amazon VPC), t2.micro reserved instance applied\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.MZU6U2429S.6YS6EN2CT7\",\"beginRange\":\"0\",\"pricePerUnit\":{\"USD\":\"0.0000000000\"}}},\"sku\":\"STTHYT3WDDQU8UBR\",\"effectiveDate\":\"2017-08-31T23:59:59Z\",\"offerTermCode\":\"MZU6U2429S\",\"termAttributes\":{\"LeaseContractLength\":\"3yr\",\"OfferingClass\":\"convertible\",\"PurchaseOption\":\"All Upfront\"}},\"STTHYT3WDDQU8UBR.VJWZNREJX2\":{\"priceDimensions\":{\"STTHYT3WDDQU8UBR.VJWZNREJX2.2TG2D8R56U\":{\"unit\":\"Quantity\",\"description\":\"Upfront Fee\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.VJWZNREJX2.2TG2D8R56U\",\"pricePerUnit\":{\"USD\":\"83\"}},\"STTHYT3WDDQU8UBR.VJWZNREJX2.6YS6EN2CT7\":{\"unit\":\"Hrs\",\"endRange\":\"Inf\",\"description\":\"Linux/UNIX (Amazon VPC), t2.micro reserved instance applied\",\"appliesTo\":[],\"rateCode\":\"STTHYT3WDDQU8UBR.VJWZNREJX2.6YS6EN2CT7\",\"beginRange\":\"0\",\"pricePerUnit\":{\"USD\":\"0.0000000000\"}}},\"sku\":\"STTHYT3WDDQU8UBR\",\"effectiveDate\":\"2017-10-31T23:59:59Z\",\"offerTermCode\":\"VJWZNREJX2\",\"termAttributes\":{\"LeaseContractLength\":\"1yr\",\"OfferingClass\":\"convertible\",\"PurchaseOption\":\"All Upfront\"}}}},\"version\":\"20210115195025\",\"publicationDate\":\"2021-01-15T19:50:25Z\"}" 277 | ], 278 | "FormatVersion": "aws_v1" 279 | } 280 | 281 | 282 | aws pricing get-products --region us-east-1 --format-version aws_v1 --service-code AmazonEC2 --filters "Type=TERM_MATCH,Field=ServiceCode,Value=AmazonEC2" "Type=TERM_MATCH,Field=location,Value=\"EU (Ireland)\"" "Type=TERM_MATCH,Field=volumeApiName,Value=io2" "Type=TERM_MATCH,Field=volumeType,Value=\"Provisioned IOPS\"" 283 | { 284 | "PriceList": [ 285 | "{\"product\":{\"productFamily\":\"Storage\",\"attributes\":{\"storageMedia\":\"SSD-backed\",\"maxThroughputvolume\":\"1000 MiB/s\",\"volumeType\":\"Provisioned IOPS\",\"maxIopsvolume\":\"64000\",\"servicecode\":\"AmazonEC2\",\"usagetype\":\"EU-EBS:VolumeUsage.io2\",\"locationType\":\"AWS Region\",\"volumeApiName\":\"io2\",\"location\":\"EU (Ireland)\",\"servicename\":\"Amazon Elastic Compute Cloud\",\"maxVolumeSize\":\"16 TiB\",\"operation\":\"\"},\"sku\":\"TN2XFMNDXRZZ5CGK\"},\"serviceCode\":\"AmazonEC2\",\"terms\":{\"OnDemand\":{\"TN2XFMNDXRZZ5CGK.JRTCKXETXF\":{\"priceDimensions\":{\"TN2XFMNDXRZZ5CGK.JRTCKXETXF.6YS6EN2CT7\":{\"unit\":\"GB-month\",\"endRange\":\"Inf\",\"description\":\"$0.138 per GB-month of Provisioned IOPS SSD (io2) provisioned storage - EU (Ireland)\",\"appliesTo\":[],\"rateCode\":\"TN2XFMNDXRZZ5CGK.JRTCKXETXF.6YS6EN2CT7\",\"beginRange\":\"0\",\"pricePerUnit\":{\"USD\":\"0.1380000000\"}}},\"sku\":\"TN2XFMNDXRZZ5CGK\",\"effectiveDate\":\"2021-01-01T00:00:00Z\",\"offerTermCode\":\"JRTCKXETXF\",\"termAttributes\":{}}}},\"version\":\"20210115195025\",\"publicationDate\":\"2021-01-15T19:50:25Z\"}" 286 | ], 287 | "FormatVersion": "aws_v1" 288 | } 289 | --------------------------------------------------------------------------------