Skip to content

Commit

Permalink
[feature] Add aws-redis-replication-group (#267)
Browse files Browse the repository at this point in the history
### Summary
This module is similar to [aws-redis-node](https://github.com/chanzuckerberg/cztack/tree/main/aws-redis-node), however it creates a Elasticache Redis cluster using a replication group.
The motivation to create this module is because encryption options (in-transit and at rest) are only available when you create your cluster using a replication group.

### Test Plan
- Manual tests (already applied to IDseq)
- Unit tests

### References
(Optional) Additional links to provide more context.
  • Loading branch information
davidrissato authored Jan 8, 2021
1 parent 68ab717 commit 1b53806
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 50 additions & 0 deletions aws-redis-replication-group/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Redis Replication Group

This module creates a Elasticache Redis cluster using
a replication group with the given parameters.

<!-- START -->
## 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. |

<!-- END -->
56 changes: 56 additions & 0 deletions aws-redis-replication-group/main.tf
Original file line number Diff line number Diff line change
@@ -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
}
60 changes: 60 additions & 0 deletions aws-redis-replication-group/module_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
14 changes: 14 additions & 0 deletions aws-redis-replication-group/outputs.tf
Original file line number Diff line number Diff line change
@@ -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
}
5 changes: 5 additions & 0 deletions aws-redis-replication-group/terraform.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
terraform {
required_providers {
aws = "< 3.0.0"
}
}
102 changes: 102 additions & 0 deletions aws-redis-replication-group/variables.tf
Original file line number Diff line number Diff line change
@@ -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."
}

0 comments on commit 1b53806

Please sign in to comment.