Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(eks-vpc): verify available ec2 elastic ips quotas before apply #215

Merged
merged 22 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ eksctl 0.200.0

golang 1.23.4

jq 1.7.1

just 1.38.0

opentofu 1.9.0
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ Consider installing Camunda 8 via [this guide](https://docs.camunda.io/docs/next

## Usage

### Prerequisites

- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html): Command-line interface for interacting with AWS.
- [jq](https://jqlang.github.io/jq/download/): A lightweight and flexible command-line JSON processor.
- bash: A command-line interpreter required for executing the verification scripts.

These tools are required because **verification scripts** are used to ensure that resources can be deployed within AWS quotas. These scripts rely on `jq` to parse the JSON data returned by the AWS CLI and `bash` for executing conditional commands.

### Example

Below is a simple example configuration for deploying both an EKS cluster, an Aurora PostgreSQL database and an OpenSearch domain.

See [AWS EKS Cluster inputs](./modules/eks-cluster/README.md#inputs), [AWS Aurora RDS inputs](./modules/aurora/README.md#inputs) and [AWS OpenSearch inputs](./modules/opensearch/README.md#inputs) for further configuration options and how they affect the cluster and database creation.
Expand Down
3 changes: 3 additions & 0 deletions modules/eks-cluster/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ module "eks_cluster" {
| [time_sleep.eks_cluster_warmup](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource |
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
| [external_external.elastic_ip_quota](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external) | data source |
| [external_external.elastic_ips_count](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external) | data source |
| [external_external.vpc_data](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external) | data source |
## Inputs

| Name | Description | Type | Default | Required |
Expand Down
26 changes: 26 additions & 0 deletions modules/eks-cluster/get_elastic_ips_count.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/bash

set -euo pipefail

# Check if a region argument is provided
if [ -z "$1" ]; then
echo "Error: No region specified."
echo "Usage: $0 <region>"
exit 1
fi

REGION=$1

# Fetch all Elastic IPs for the specified region using AWS CLI
eips=$(aws ec2 describe-addresses --region "$REGION" --query 'Addresses[*].{PublicIp:PublicIp,AllocationId:AllocationId,InstanceId:InstanceId}' --output json)

# Check if the AWS CLI command was successful
if [ $? -ne 0 ]; then
echo "Error: Failed to fetch Elastic IPs for region $REGION."
exit 1
fi

eips_count=$(echo "$eips" | jq length)

# Return the quota value in a format Terraform's external data source expects (string: string)
echo "{\"elastic_ips_count\": \"$eips_count\"}"
24 changes: 24 additions & 0 deletions modules/eks-cluster/get_elastic_ips_quota.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash

set -euo pipefail

# Check if a region argument is provided
if [ -z "$1" ]; then
echo "Error: No region specified."
echo "Usage: $0 <region>"
exit 1
fi

REGION=$1

# Fetch the Elastic IP quota for the specified region using AWS CLI
quota=$(aws service-quotas get-service-quota --region "$REGION" --service-code ec2 --quota-code L-0263D0A3 --query 'Quota.Value' --output text)

# Check if the AWS CLI command for quota was successful
if [ $? -ne 0 ]; then
echo "Error: Failed to fetch Elastic IP quota for region $REGION."
exit 1
fi

# Return the quota value in a format Terraform's external data source expects (string: string)
echo "{\"quota\": \"$quota\"}"
34 changes: 34 additions & 0 deletions modules/eks-cluster/get_vpc_count.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash

set -euo pipefail

# Count the number of VPCs matching a query name

# Check if a region argument is provided
if [ -z "$1" ]; then
echo "Error: No region specified."
echo "Usage: $0 <region>"
exit 1
fi

REGION=$1
VPC_NAME=$2

# Fetch VPC details based on the name
vpcs=$(aws ec2 describe-vpcs \
--region "$REGION" \
--filters "Name=tag:Name,Values=$VPC_NAME" \
--query 'Vpcs[*].{VpcId:VpcId,CidrBlock:CidrBlock}' \
--output json)

# Check if the AWS CLI command was successful
if [ $? -ne 0 ]; then
echo "Error: Failed to fetch VPC data for region $REGION and VPC name $VPC_NAME."
exit 1
fi

# Parse VPC details and count
vpc_count=$(echo "$vpcs" | jq length)

# Return the VPC data in a format Terraform's external data source expects
echo "{\"vpc_count\": \"$vpc_count\"}"
30 changes: 29 additions & 1 deletion modules/eks-cluster/vpc.tf
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,35 @@ locals {
]
}

data "external" "elastic_ip_quota" {
program = ["bash", "./get_elastic_ips_quota.sh", var.region]
}


data "external" "elastic_ips_count" {
program = ["bash", "./get_elastic_ips_count.sh", var.region]
}

# Data source to check if the VPC exists
data "external" "vpc_data" {
program = ["bash", "./get_vpc_count.sh", var.region, local.vpc_name]
}


check "elastic_ip_quota_check" {

# Only check the condition when no existing vpc is there
assert {
condition = tonumber(data.external.vpc_data.result.vpc_count) > 0 || tonumber(data.external.elastic_ip_quota.result.quota) >= length(local.azs)
error_message = "The Elastic IP quota is insufficient to cover all local availability zones (need: ${length(local.azs)}, have: ${tonumber(data.external.elastic_ip_quota.result.quota)})."
}

assert {
condition = tonumber(data.external.vpc_data.result.vpc_count) > 0 || (tonumber(data.external.elastic_ip_quota.result.quota) - tonumber(data.external.elastic_ips_count.result.elastic_ips_count)) >= length(local.azs)
error_message = "Not enough available Elastic IPs to cover all local availability zones (need: ${length(local.azs)}, have: ${(tonumber(data.external.elastic_ip_quota.result.quota) - tonumber(data.external.elastic_ips_count.result.elastic_ips_count))})."
}
}


module "vpc" {
source = "terraform-aws-modules/vpc/aws"
Expand Down Expand Up @@ -59,5 +88,4 @@ module "vpc" {
enable_flow_log = false
create_flow_log_cloudwatch_iam_role = false
create_flow_log_cloudwatch_log_group = false

}
Loading