diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07fbeb8b..35b89735 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,6 +111,7 @@ jobs: - aws-params-secrets-setup - aws-params-writer - aws-redis-node + - aws-redis-replication-group - aws-s3-private-bucket - aws-s3-public-bucket - aws-single-page-static-site diff --git a/aws-redis-replication-group/README.md b/aws-redis-replication-group/README.md new file mode 100644 index 00000000..4dfc50b3 --- /dev/null +++ b/aws-redis-replication-group/README.md @@ -0,0 +1,50 @@ +# Redis Replication Group + +This module creates a Elasticache Redis cluster using +a replication group with the given parameters. + + +## Requirements + +| Name | Version | +|------|---------| +| aws | < 3.0.0 | + +## Providers + +| Name | Version | +|------|---------| +| aws | < 3.0.0 | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| at\_rest\_encryption\_enabled | Whether to enable encryption at rest. | `bool` | `false` | no | +| apply\_immediately | Whether changes should be applied immediately or during the next maintenance window. | `bool` | `true` | no | +| availability\_zone | Availability zone in which this instance should run. | `string` | `null` | no | +| engine\_version | The version of Redis to run. See [supported versions](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/supported-engine-versions.html) | `string` | `"5.0.5"` | no | +| env | Env for tagging and naming. See [doc](../README.md#consistent-tagging). | `string` | n/a | yes | +| ingress\_security\_group\_ids | Source security groups which should be able to contact this instance. | `list(string)` | n/a | yes | +| instance\_type | The type of instance to run. See [supported node types](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/CacheNodes.SupportedTypes.html) | `string` | `"cache.m5.large"` | no | +| number_cache_clusters | Number of cache clusters. | `number` | 1 | no | +| owner | Owner for tagging and naming. See [doc](../README.md#consistent-tagging). | `string` | n/a | yes | +| parameter\_group\_name | Parameter group to use for this Redis cache. | `string` | `"default.redis5.0"` | no | +| port | Port to host Redis on. | `number` | `6379` | no | +| project | Project for tagging and naming. See [doc](../README.md#consistent-tagging) | `string` | n/a | yes | +| replication\_group\_description | A user-created description for the replication group. | `string` | n/a | yes | +| resource\_name | If not set, name will be [var.project]-[var.env]-[var.name]. | `string` | `""` | no | +| service | Service for tagging and naming. See [doc](../README.md#consistent-tagging) | `string` | `"redis"` | no | +| subnets | List of subnets to which this EC instance should be attached. They should probably be private. | `list(string)` | n/a | yes | +| transit\_encryption\_enabled | Whether to enable encryption in transit. | `bool` | `false` | no | +| vpc\_id | VPC where the cache will be deployed. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| primary_endpoint_address | The endpoint of the primary node in this node group (shard). | +| configuration_endpoint_address | The configuration endpoint address to allow host discovery. | +| port | Redis TCP port. | + + diff --git a/aws-redis-replication-group/main.tf b/aws-redis-replication-group/main.tf new file mode 100755 index 00000000..31c81967 --- /dev/null +++ b/aws-redis-replication-group/main.tf @@ -0,0 +1,56 @@ +locals { + name = "${var.project}-${var.env}-${var.service}" + + tags = { + managedBy = "terraform" + Name = "${var.project}-${var.env}-${var.service}" + project = var.project + env = var.env + service = var.service + owner = var.owner + } +} + +module "sg" { + source = "terraform-aws-modules/security-group/aws" + version = "3.12.0" + name = local.name + description = "Allow traffic to Redis." + vpc_id = var.vpc_id + tags = local.tags + + ingress_with_source_security_group_id = [ + for sg in var.ingress_security_group_ids : { + from_port = var.port + to_port = var.port + protocol = "tcp" + description = "Redis port" + source_security_group_id = sg + } + ] + + egress_rules = ["all-all"] +} + +resource "aws_elasticache_subnet_group" "default" { + name = var.resource_name != "" ? var.resource_name : local.name + subnet_ids = var.subnets +} + +resource "aws_elasticache_replication_group" "default" { + replication_group_id = var.resource_name != "" ? var.resource_name : local.name + replication_group_description = var.replication_group_description + engine = "redis" + engine_version = var.engine_version + node_type = var.instance_type + port = var.port + number_cache_clusters = var.number_cache_clusters + parameter_group_name = var.parameter_group_name + subnet_group_name = aws_elasticache_subnet_group.default.name + security_group_ids = [module.sg.this_security_group_id] + apply_immediately = var.apply_immediately + at_rest_encryption_enabled = var.at_rest_encryption_enabled + transit_encryption_enabled = var.transit_encryption_enabled + availability_zones = var.availability_zones + tags = local.tags +} diff --git a/aws-redis-replication-group/module_test.go b/aws-redis-replication-group/module_test.go new file mode 100644 index 00000000..b75f6583 --- /dev/null +++ b/aws-redis-replication-group/module_test.go @@ -0,0 +1,60 @@ +package test + +import ( + "fmt" + "log" + "os" + "testing" + + "github.com/chanzuckerberg/go-misc/tftest" + "github.com/gruntwork-io/terratest/modules/terraform" +) + +func TestAWSRedisReplicationGroup(t *testing.T) { + test := tftest.Test{ + + Setup: func(t *testing.T) *terraform.Options { + privateSubnets := tftest.ListEnvVar("PRIVATE_SUBNETS") + log.Printf("subnets %#v\n", privateSubnets) + log.Printf("subnets %#v\n", os.Getenv("PRIVATE_SUBNETS")) + vpc := tftest.EnvVar(tftest.EnvVPCID) + + sg := tftest.CreateSecurityGroup(t, tftest.DefaultRegion, vpc) + + project := tftest.UniqueID() + env := tftest.UniqueID() + service := tftest.UniqueID() + owner := tftest.UniqueID() + replication_group_description := tftest.UniqueID() + transit_encryption_enabled := true + at_rest_encryption_enabled := true + + az := fmt.Sprintf("%sa", tftest.DefaultRegion) + + return tftest.Options(tftest.DefaultRegion, + map[string]interface{}{ + "project": project, + "env": env, + "service": service, + "owner": owner, + + "availability_zones": []string{az}, + "subnets": privateSubnets, + "ingress_security_group_ids": []string{sg}, + "vpc_id": vpc, + + "replication_group_description": replication_group_description, + "transit_encryption_enabled": transit_encryption_enabled, + "at_rest_encryption_enabled": at_rest_encryption_enabled, + }, + ) + }, + Validate: func(t *testing.T, options *terraform.Options) {}, + + Cleanup: func(t *testing.T, options *terraform.Options) { + tftest.DeleteSecurityGroup(t, tftest.DefaultRegion, options.Vars["ingress_security_group_ids"].([]interface{})[0].(string)) + }, + } + + test.Run(t) +} diff --git a/aws-redis-replication-group/outputs.tf b/aws-redis-replication-group/outputs.tf new file mode 100755 index 00000000..cc0a332d --- /dev/null +++ b/aws-redis-replication-group/outputs.tf @@ -0,0 +1,14 @@ +output "primary_endpoint_address" { + description = "The endpoint of the primary node in this node group (shard)." + value = aws_elasticache_replication_group.default.primary_endpoint_address +} + +output "configuration_endpoint_address" { + description = "The configuration endpoint address to allow host discovery." + value = aws_elasticache_replication_group.default.configuration_endpoint_address +} + +output "port" { + description = "Redis TCP port." + value = var.port +} diff --git a/aws-redis-replication-group/terraform.tf b/aws-redis-replication-group/terraform.tf new file mode 100644 index 00000000..afd01df4 --- /dev/null +++ b/aws-redis-replication-group/terraform.tf @@ -0,0 +1,5 @@ +terraform { + required_providers { + aws = "< 3.0.0" + } +} diff --git a/aws-redis-replication-group/variables.tf b/aws-redis-replication-group/variables.tf new file mode 100755 index 00000000..67869a88 --- /dev/null +++ b/aws-redis-replication-group/variables.tf @@ -0,0 +1,102 @@ +variable "project" { + type = string + description = "Project for tagging and naming. See [doc](../README.md#consistent-tagging)" +} + +variable "env" { + type = string + description = "Env for tagging and naming. See [doc](../README.md#consistent-tagging)." +} + +variable "service" { + type = string + description = "Service for tagging and naming. See [doc](../README.md#consistent-tagging)" + default = "redis" +} + +variable "owner" { + type = string + description = "Owner for tagging and naming. See [doc](../README.md#consistent-tagging)." +} + +variable "subnets" { + type = list(string) + description = "List of subnets to which this EC instance should be attached. They should probably be private." +} + +variable "availability_zones" { + type = list(string) + description = "Availability zone in which this instance should run." + default = null +} + +variable "ingress_security_group_ids" { + type = list(string) + description = "Source security groups which should be able to contact this instance." +} + +variable "port" { + type = number + description = "Port to host Redis on." + default = 6379 +} + +variable "instance_type" { + type = string + description = "The type of instance to run. See [supported node types](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/CacheNodes.SupportedTypes.html)" + default = "cache.m5.large" +} + +variable "parameter_group_name" { + type = string + description = "Parameter group to use for this Redis cache." + default = "default.redis5.0" +} + +variable "engine_version" { + type = string + description = "The version of Redis to run. See [supported versions](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/supported-engine-versions.html)" + default = "5.0.5" +} + +variable "apply_immediately" { + type = bool + description = "Whether changes should be applied immediately or during the next maintenance window." + default = true +} + +# This is to get a around a limitation where the elasticache cluster id can be +# only 20 characters long. Use it only if you get that error. +variable "resource_name" { + description = "If not set, name will be [var.project]-[var.env]-[var.name]." + type = string + default = "" +} + +variable "vpc_id" { + type = string + description = "VPC where the cache will be deployed." +} + +variable "number_cache_clusters" { + type = number + description = "Number of cache clusters. Default 1." + default = 1 +} + +variable "at_rest_encryption_enabled" { + type = bool + description = "Whether to enable encryption at rest. Default: false." + default = false +} + +variable "transit_encryption_enabled" { + type = bool + description = "Whether to enable encryption in transit. Default: false." + default = false +} + +variable "replication_group_description" { + type = string + description = "A user-created description for the replication group." +}