Skip to content

Commit

Permalink
Add Redis Cache module with tests and example usage
Browse files Browse the repository at this point in the history
Introduced a new Terraform module for provisioning Azure Redis Cache with support for private endpoints and authentication. Added test infrastructure, example usage in `README.md`, and variable configurations to ensure flexibility and reusability.
  • Loading branch information
ffppa committed Jan 27, 2025
1 parent b62d81c commit 4e1b1a2
Show file tree
Hide file tree
Showing 12 changed files with 512 additions and 0 deletions.
103 changes: 103 additions & 0 deletions redis_cache/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Redis cache

This module allows the creation of a redis cache

## How to use

```ts
resource "azurerm_resource_group" "redis" {
name = "${local.project}-redis-rg"
location = var.location
tags = var.tags
}

## redisbase subnet
module "redis_snet" {
source = "git::https://github.com/pagopa/terraform-azurerm-v3.git//subnet?ref=v8.8.0"
name = "${local.project}-redis-snet"
address_prefixes = var.cidr_subnet_redis
resource_group_name = azurerm_resource_group.rg_vnet.name
virtual_network_name = module.vnet.name
}

module "redis" {
count = var.redis_enabled ? 1 : 0
source = "git::https://github.com/pagopa/terraform-azurerm-v3.git//redis_cache?ref=v8.8.0"
name = "${local.project}-redis"
resource_group_name = azurerm_resource_group.redis.name
location = azurerm_resource_group.redis.location
capacity = 1
enable_non_ssl_port = false
family = "P"
sku_name = "Premium"
enable_authentication = true

private_endpoint = {
enabled = true
virtual_network_id = module.vnet.id
subnet_id = module.redis_snet.id
private_dns_zone_ids = [azurerm_private_dns_zone.internal_devopslab[0].id]
}

tags = var.tags
}
```

<!-- markdownlint-disable -->
<!-- BEGIN_TF_DOCS -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.9.0 |
| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 4 |

## Modules

No modules.

## Resources

| Name | Type |
|------|------|
| [azurerm_private_endpoint.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/private_endpoint) | resource |
| [azurerm_redis_cache.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/redis_cache) | resource |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_backup_configuration"></a> [backup\_configuration](#input\_backup\_configuration) | n/a | <pre>object({<br/> frequency = number<br/> max_snapshot_count = number<br/> storage_connection_string = string<br/> })</pre> | `null` | no |
| <a name="input_capacity"></a> [capacity](#input\_capacity) | The size of the Redis cache to deploy | `number` | `1` | no |
| <a name="input_data_persistence_authentication_method"></a> [data\_persistence\_authentication\_method](#input\_data\_persistence\_authentication\_method) | (Optional) Preferred auth method to communicate to storage account used for data persistence. Possible values are SAS and ManagedIdentity. Defaults to SAS. | `string` | `"SAS"` | no |
| <a name="input_enable_authentication"></a> [enable\_authentication](#input\_enable\_authentication) | If set to false, the Redis instance will be accessible without authentication. Defaults to true. | `bool` | `true` | no |
| <a name="input_enable_non_ssl_port"></a> [enable\_non\_ssl\_port](#input\_enable\_non\_ssl\_port) | Enable the non-SSL port (6379) - disabled by default. | `bool` | `false` | no |
| <a name="input_family"></a> [family](#input\_family) | The SKU family/pricing group to use | `string` | n/a | yes |
| <a name="input_location"></a> [location](#input\_location) | The location of the resource group. | `string` | n/a | yes |
| <a name="input_name"></a> [name](#input\_name) | The name of the Redis instance. | `string` | n/a | yes |
| <a name="input_patch_schedules"></a> [patch\_schedules](#input\_patch\_schedules) | n/a | <pre>list(object({<br/> day_of_week = string<br/> start_hour_utc = number<br/> }))</pre> | `[]` | no |
| <a name="input_private_endpoint"></a> [private\_endpoint](#input\_private\_endpoint) | (Required) Enable private endpoint with required params | <pre>object({<br/> enabled = bool<br/> virtual_network_id = string<br/> subnet_id = string<br/> private_dns_zone_ids = list(string)<br/> })</pre> | n/a | yes |
| <a name="input_private_static_ip_address"></a> [private\_static\_ip\_address](#input\_private\_static\_ip\_address) | The Static IP Address to assign to the Redis Cache when hosted inside the Virtual Network | `string` | `null` | no |
| <a name="input_public_network_access_enabled"></a> [public\_network\_access\_enabled](#input\_public\_network\_access\_enabled) | Whether or not public network access is allowed for this Redis Cache. true means this resource could be accessed by both public and private endpoint. false means only private endpoint access is allowed. Defaults to false. | `string` | `false` | no |
| <a name="input_redis_version"></a> [redis\_version](#input\_redis\_version) | The version of Redis to use: 4 (deprecated) or 6 | `string` | n/a | yes |
| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | n/a | `string` | n/a | yes |
| <a name="input_shard_count"></a> [shard\_count](#input\_shard\_count) | The number of Shards to create on the Redis Cluster. | `number` | `null` | no |
| <a name="input_sku_name"></a> [sku\_name](#input\_sku\_name) | The SKU of Redis to use | `string` | n/a | yes |
| <a name="input_subnet_id"></a> [subnet\_id](#input\_subnet\_id) | The Subnet within which the Redis Cache should be deployed (Deprecated, use private\_endpoint) | `string` | `null` | no |
| <a name="input_tags"></a> [tags](#input\_tags) | n/a | `map(any)` | n/a | yes |
| <a name="input_zones"></a> [zones](#input\_zones) | Specifies a list of Availability Zones in which this Redis Cache should be located. Changing this forces a new Redis Cache to be created. | `list(number)` | n/a | yes |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_hostname"></a> [hostname](#output\_hostname) | n/a |
| <a name="output_id"></a> [id](#output\_id) | n/a |
| <a name="output_location"></a> [location](#output\_location) | n/a |
| <a name="output_name"></a> [name](#output\_name) | n/a |
| <a name="output_port"></a> [port](#output\_port) | n/a |
| <a name="output_primary_access_key"></a> [primary\_access\_key](#output\_primary\_access\_key) | n/a |
| <a name="output_primary_connection_string"></a> [primary\_connection\_string](#output\_primary\_connection\_string) | n/a |
| <a name="output_resource_group_name"></a> [resource\_group\_name](#output\_resource\_group\_name) | n/a |
| <a name="output_ssl_port"></a> [ssl\_port](#output\_ssl\_port) | n/a |
<!-- END_TF_DOCS -->
67 changes: 67 additions & 0 deletions redis_cache/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
resource "azurerm_redis_cache" "this" {
name = var.name
location = var.location
resource_group_name = var.resource_group_name
capacity = var.capacity
shard_count = var.shard_count
enable_non_ssl_port = var.enable_non_ssl_port
minimum_tls_version = "1.2"
subnet_id = var.subnet_id
private_static_ip_address = var.private_static_ip_address
family = var.family
sku_name = var.sku_name
public_network_access_enabled = var.public_network_access_enabled
redis_version = var.redis_version
zones = var.sku_name == "Premium" ? var.zones : null

redis_configuration {
enable_authentication = var.enable_authentication
rdb_backup_enabled = var.backup_configuration != null
rdb_backup_frequency = var.backup_configuration != null ? var.backup_configuration.frequency : null
rdb_backup_max_snapshot_count = var.backup_configuration != null ? var.backup_configuration.max_snapshot_count : null
rdb_storage_connection_string = var.backup_configuration != null ? var.backup_configuration.storage_connection_string : null

data_persistence_authentication_method = var.data_persistence_authentication_method
}

dynamic "patch_schedule" {
for_each = var.patch_schedules
iterator = schedule
content {
day_of_week = schedule.value.day_of_week
start_hour_utc = schedule.value.start_hour_utc
}
}

tags = var.tags

# NOTE: There's a bug in the Redis API where the original storage connection string isn't being returned,
# which is being tracked here [https://github.com/Azure/azure-rest-api-specs/issues/3037].
# In the interim we use the ignore_changes attribute to ignore changes to this field.
lifecycle {
ignore_changes = [redis_configuration[0].rdb_storage_connection_string]
}
}

resource "azurerm_private_endpoint" "this" {
count = var.private_endpoint.enabled ? 1 : 0

name = "${azurerm_redis_cache.this.name}-private-endpoint"
location = var.location
resource_group_name = var.resource_group_name
subnet_id = var.private_endpoint.subnet_id

private_dns_zone_group {
name = "${azurerm_redis_cache.this.name}-private-dns-zone-group"
private_dns_zone_ids = var.private_endpoint.private_dns_zone_ids
}

private_service_connection {
name = "${azurerm_redis_cache.this.name}-private-service-connection"
private_connection_resource_id = azurerm_redis_cache.this.id
is_manual_connection = false
subresource_names = ["redisCache"]
}

tags = var.tags
}
37 changes: 37 additions & 0 deletions redis_cache/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
output "id" {
value = azurerm_redis_cache.this.id
}

output "name" {
value = azurerm_redis_cache.this.name
}

output "location" {
value = azurerm_redis_cache.this.location
}

output "resource_group_name" {
value = azurerm_redis_cache.this.resource_group_name
}

output "primary_access_key" {
value = azurerm_redis_cache.this.primary_access_key
sensitive = true
}

output "primary_connection_string" {
value = azurerm_redis_cache.this.primary_connection_string
sensitive = true
}

output "hostname" {
value = azurerm_redis_cache.this.hostname
}

output "port" {
value = azurerm_redis_cache.this.port
}

output "ssl_port" {
value = azurerm_redis_cache.this.ssl_port
}
12 changes: 12 additions & 0 deletions redis_cache/tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Test for Azure module

Terraform template to test the module.

You need the access to DevOpsLab Subscription or change backend.ini value.

`resources.tf` file contains all resources to test.

## How to use it
- ./terraform.sh plan
- ./terraform.sh apply
- ./terraform.sh destroy
1 change: 1 addition & 0 deletions redis_cache/tests/backend.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
subscription=DevOpsLab
29 changes: 29 additions & 0 deletions redis_cache/tests/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
terraform {
required_version = ">= 1.3.0"

required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 3.30.0, <= 3.94.0"
}
}
}

provider "azurerm" {
features {
key_vault {
purge_soft_delete_on_destroy = false
}
resource_group {
prevent_deletion_if_contains_resources = false
}
}
}

resource "random_id" "unique" {
byte_length = 3
}

locals {
project = "${var.prefix}${random_id.unique.hex}"
}
3 changes: 3 additions & 0 deletions redis_cache/tests/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "resource_group_name" {
value = azurerm_resource_group.rg.name
}
67 changes: 67 additions & 0 deletions redis_cache/tests/resources.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
resource "azurerm_resource_group" "rg" {
name = "${local.project}-rg"
location = var.location

tags = var.tags
}

resource "azurerm_virtual_network" "vnet" {
name = "${local.project}-vnet"
address_space = var.vnet_address_space
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location

tags = var.tags
}

resource "azurerm_subnet" "private_endpoint_subnet" {
name = "${local.project}-subnet"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = var.private_endpoint_subnet_cidr
}

resource "azurerm_private_dns_zone" "privatelink_redis_cache_windows_net" {
name = "privatelink.redis.cache.windows.net"
resource_group_name = azurerm_resource_group.rg.name

tags = var.tags
}

resource "azurerm_private_dns_zone_virtual_network_link" "privatelink_redis_cache_windows_net_vnet" {
name = azurerm_virtual_network.vnet.name
resource_group_name = azurerm_resource_group.rg.name
private_dns_zone_name = azurerm_private_dns_zone.privatelink_redis_cache_windows_net.name
virtual_network_id = azurerm_virtual_network.vnet.id
registration_enabled = false

tags = var.tags
}

#
# REDIS
#
module "redis_cache" {
source = "../../redis_cache"

name = "${local.project}-redis"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location

redis_version = 6
capacity = 1
enable_non_ssl_port = false
family = "P"
sku_name = "Premium"
enable_authentication = true
zones = [1, 2, 3]

private_endpoint = {
enabled = true
virtual_network_id = azurerm_virtual_network.vnet.id
subnet_id = azurerm_subnet.private_endpoint_subnet.id
private_dns_zone_ids = [azurerm_private_dns_zone.privatelink_redis_cache_windows_net.id]
}

tags = var.tags
}
34 changes: 34 additions & 0 deletions redis_cache/tests/terraform.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash

set -e

action=$1
shift 1
other=$@

subscription="MOCK_VALUE"

case $action in
"init" | "apply" | "plan" | "destroy" )
# shellcheck source=/dev/null
if [ -e "./backend.ini" ]; then
source ./backend.ini
else
echo "Error: no backend.ini found!"
exit 1
fi

az account set -s "${subscription}"

terraform init
terraform "$action" $other
;;
"clean" )
rm -rf .terraform* terraform.tfstate*
echo "cleaned..."
;;
* )
echo "Missed action: init, apply, plan, destroy clean"
exit 1
;;
esac
32 changes: 32 additions & 0 deletions redis_cache/tests/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
variable "prefix" {
description = "Resorce prefix"
type = string
default = "azrmtest"
}

variable "location" {
description = "Resorce location"
type = string
default = "westeurope"
}

variable "tags" {
type = map(string)
description = "Azurerm test tags"
default = {
CreatedBy = "Terraform"
Source = "https://github.com/pagopa/terraform-azurerm-v3"
}
}

### Custom variables

variable "vnet_address_space" {
type = list(string)
default = ["10.0.0.0/16"]
}

variable "private_endpoint_subnet_cidr" {
type = list(string)
default = ["10.0.1.0/26"]
}
Loading

0 comments on commit 4e1b1a2

Please sign in to comment.