Skip to content

Commit d2f0b0f

Browse files
authored
doc: Implements v2 and v3 MongoDB Atlas cluster migration examples (#3050)
* initial v2 & v3 implementations * Adds `v2` Implementation Changes and Highlights * doc: Update README with v3 implementation changes and highlights * doc: Update README and add v1_v2 and v3 variable files for cluster migration examples * style: fmt * doc: Fix v3 usage of cluster data source * feat: Add output for MongoDB connection strings in v1, v2, and v3 examples * style: fmt * chore: Enable Advanced Cluster V2 Schema validation in tf-validate script * fix lint errors * feat: Add function to check for V2 schema directories in tf-validate script * refactor: Use approach 1 (file headings) rather than a table * address PR comments * docs: Update README files to enhance migration guidance for mongodbatlas_cluster to mongodbatlas_advanced_cluster * address PR comments * chore: Update ToC * Review suggestions * update var name * docs: Enhance README to clarify standalone usage and upgrade support for module_maintainer * fix: Update required_version comment for clarity in versions.tf files * docs: Add v3_no_plan_changes and step for ensuring no changes when using new `replication_specs_new` variable * update var name to make tf-validate pass * fix: Update environment variable for Terraform validation in v2 schema * docs: Update ToC * fix: update restore command in tf-validate script to use preview_provider_v2.go
1 parent ac891f5 commit d2f0b0f

23 files changed

+962
-71
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,140 @@
11
# Module Maintainer - `mongodbatlas_cluster` to `mongodbatlas_advanced_cluster`
22

33
The purpose of this example is to demonstrate how a Terraform module can help in the migration from `mongodbatlas_cluster` to `mongodbatlas_advanced_cluster`.
4-
The example contains three module versions:
4+
The example contains three module versions which represent the three steps of the migration:
55

6-
<!-- Update Variable count -->
7-
Version | Purpose | Variables | Resources
6+
Step | Purpose | Resources
87
--- | --- | --- | ---
9-
[v1](./v1) | Baseline | 5 | `mongodbatlas_cluster`
10-
[v2](./v2) | Migrate to advanced_cluster with no change in variables or plan | 5 | `mongodbatlas_advanced_cluster`
11-
[v3](./v3) | Use the latest features of advanced_cluster | 10 | `mongodbatlas_advanced_cluster`
8+
[Step 1](./v1) | Baseline | `mongodbatlas_cluster`
9+
[Step 2](./v2) | Migrate to advanced_cluster with no change in variables or plan | `mongodbatlas_advanced_cluster`
10+
[Step 3](./v3) | Use the latest features of advanced_cluster | `mongodbatlas_advanced_cluster`
1211

13-
<!-- TODO: Add highlights of module implementations -->
12+
The rest of this document summarizes the different implementations:
13+
14+
- [Step 1: Module `v1` Implementation Summary](#step-1-module-v1-implementation-summary)
15+
- [`variables.tf`](#variablestf)
16+
- [`main.tf`](#maintf)
17+
- [`outputs.tf`](#outputstf)
18+
- [Step 2: Module `v2` Implementation Changes and Highlights](#step-2-module-v2-implementation-changes-and-highlights)
19+
- [`variables.tf` unchanged from `v1`](#variablestf-unchanged-from-v1)
20+
- [`versions.tf`](#versionstf)
21+
- [`main.tf`](#maintf-1)
22+
- [`outputs.tf`](#outputstf-1)
23+
- [Step 3: Module `v3` Implementation Changes and Highlights](#step-3-module-v3-implementation-changes-and-highlights)
24+
- [`variables.tf`](#variablestf-1)
25+
- [`main.tf`](#maintf-2)
26+
- [`outputs.tf`](#outputstf-2)
27+
28+
29+
## Step 1: Module `v1` Implementation Summary
30+
This module creates a `mongodbatlas_cluster`
31+
32+
### [`variables.tf`](v1/variables.tf)
33+
An abstraction for the `mongodbatlas_cluster` resource:
34+
- Not all arguments are exposed, but the arguments follow the schema closely
35+
- `disk_size` and `auto_scaling_disk_gb_enabled` are mutually exclusive and validated in the `precondition` in `main.tf`
36+
37+
### [`main.tf`](v1/main.tf)
38+
It uses `dynamic` blocks to represent:
39+
- `tags`
40+
- `replication_specs`
41+
- `regions_config` (nested inside replication_specs)
42+
43+
### [`outputs.tf`](v1/outputs.tf)
44+
- Expose some attributes of `mongodbatlas_cluster` but also the full resource with `mongodbatlas_cluster` output variable:
45+
```terraform
46+
output "mongodbatlas_cluster" {
47+
value = mongodbatlas_cluster.this
48+
description = "Full cluster configuration for mongodbatlas_cluster resource"
49+
}
50+
```
51+
52+
## Step 2: Module `v2` Implementation Changes and Highlights
53+
This module uses HCL code to create a `mongodbatlas_advanced_cluster` resource that is compatible with the input variables of `v1`.
54+
The module supports standalone usage when there is no existing `mongodbatlas_cluster` and also upgrading from `v1` using a `moved` block.
55+
56+
### [`variables.tf`](v2/variables.tf) unchanged from `v1`
57+
### [`versions.tf`](v2/versions.tf)
58+
- `required_version` of Terraform CLI bumped to `# todo: minimum moved block supported version` for `moved` block support
59+
- `mongodbatlas.version` bumped to `# todo: PLACEHOLDER_TPF_RELEASE` for new `mongodbatlas_advanced_cluster` v2 schema support
60+
61+
### [`main.tf`](v2/main.tf)
62+
<!-- TODO: Update link to (schema v2) docs page -->
63+
- `locals.replication_specs` an intermediate variable transforming the `variables` to a compatible [replication_specs](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/advanced_cluster#replication_specs-1) for `mongodbatlas_advanced_cluster`
64+
- We use the Terraform builtin [range](https://developer.hashicorp.com/terraform/language/functions/range) function (`range(old_spec.num_shards)`) to flatten `num_shards`
65+
- We expand `read_only_specs` and `electable_specs` into nested attributes
66+
- We use the `var.provider_name` in the `region_configs.*.instance_size`
67+
- `moved` block:
68+
```terraform
69+
moved {
70+
from = mongodbatlas_cluster.this
71+
to = mongodbatlas_advanced_cluster.this
72+
}
73+
```
74+
- `resource "mongodbatlas_advanced_cluster" "this"`
75+
- We reference the `local.replication_specs` as input to `replication_specs` (`replication_specs = local.replication_specs`)
76+
- Tags can be passed directly instead of the dynamic block (`tags = var.tags`)
77+
- Adds `data "mongodbatlas_cluster" "this"` to avoid breaking changes in `outputs.tf` (see below)
78+
79+
### [`outputs.tf`](v2/outputs.tf)
80+
- All outputs can use `mongodbatlas_advanced_cluster` except for
81+
- `replication_specs`, we use the `data.mongodbatlas_cluster.this.replication_specs` to keep the same format
82+
- `mongodbatlas_cluster`, we use the `data.mongodbatlas_cluster.this` to keep the same format
83+
84+
85+
## Step 3: Module `v3` Implementation Changes and Highlights
86+
This module adds variables to support the latest `mongodbatlas_advanced_cluster` features while staying compatible with the old input variables.
87+
The module supports standalone usage when there is no existing `mongodbatlas_cluster` and also upgrading from `v1` using a `moved` block.
88+
The module also supports changing an existing `mongodbatlas_advanced_cluster` created in `v2`.
89+
90+
### [`variables.tf`](v3/variables.tf)
91+
- Add `replication_specs_new`, this is almost a full mirror of the `replication_specs` of the latest `mongodbatlas_advanced_cluster` schema
92+
- Use a `[]` for default to allow continued usage of the old `replication_specs`
93+
- Usage of `optional` to simplify the caller
94+
- Add `default` to `instance_size` and `provider_name` as these are not required when `replication_specs_new` is used
95+
- Change `[]` default to `replication_specs` to allow usage of `replication_specs_new`
96+
97+
### [`main.tf`](v3/main.tf)
98+
- Add *defaults* to old variables in `locals`:
99+
- `old_disk_size`
100+
- `old_instance_size`
101+
- `old_provider_name`
102+
- Add `_old` suffix to `locals.replication_specs` to make conditional code (see below) more readable
103+
- Add `precondition` for `replication_specs` to validate only `var.replication_specs_new` or `replication_specs` is used
104+
```terraform
105+
precondition {
106+
condition = !((local.use_new_replication_specs && length(var.replication_specs) > 0) || (!local.use_new_replication_specs && length(var.replication_specs) == 0))
107+
error_message = "Must use either replication_specs_new or replication_specs, not both."
108+
}
109+
```
110+
- Use a conditional for`replication_specs` in `resource "mongodbatlas_advanced_cluster" "this"`:
111+
```terraform
112+
# other attributes...
113+
replication_specs = local.use_new_replication_specs ? var.replication_specs_new : local.replication_specs_old
114+
tags = var.tags
115+
}
116+
```
117+
- Use `count` for data source to avoid error when Asymmetric Shards are used:
118+
```terraform
119+
data "mongodbatlas_cluster" "this" {
120+
count = local.use_new_replication_specs ? 0 : 1 # Not safe when Asymmetric Shards are used
121+
name = mongodbatlas_advanced_cluster.this.name
122+
project_id = mongodbatlas_advanced_cluster.this.project_id
123+
124+
depends_on = [mongodbatlas_advanced_cluster.this]
125+
}
126+
```
127+
128+
### [`outputs.tf`](v3/outputs.tf)
129+
- Update `replication_specs` and `mongodbatlas_cluster` to handle the case when the new schema is used:
130+
```terraform
131+
output "replication_specs" {
132+
value = local.use_new_replication_specs ? [] : data.mongodbatlas_cluster.this[0].replication_specs # updated
133+
description = "Replication Specs for cluster, will be empty if var.replication_specs_new is set"
134+
}
135+
136+
output "mongodbatlas_cluster" {
137+
value = local.use_new_replication_specs ? null : data.mongodbatlas_cluster.this[0] # updated
138+
description = "Full cluster configuration for mongodbatlas_cluster resource, will be null if var.replication_specs_new is set"
139+
}
140+
```

examples/migrate_cluster_to_advanced_cluster/module_maintainer/v1/outputs.tf

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
output "mongodb_raw_connection_strings" {
1+
output "mongodb_connection_strings" {
22
value = mongodbatlas_cluster.this.connection_strings
3-
description = "This is the raw list of MongoDB Atlas connection strings. Note, these do not show the connection mechanism of the database details"
3+
description = "These are the MongoDB Atlas connection strings. Note that these do not show the connection mechanism of the database details"
44
}
55

66
output "cluster_name" {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
locals {
2+
disk_size = var.auto_scaling_disk_gb_enabled ? null : var.disk_size
3+
replication_specs = flatten([
4+
for old_spec in var.replication_specs : [
5+
for shard in range(old_spec.num_shards) : [
6+
{
7+
zone_name = old_spec.zone_name
8+
region_configs = tolist([
9+
for region in old_spec.regions_config : {
10+
region_name = region.region_name
11+
provider_name = var.provider_name
12+
electable_specs = {
13+
instance_size = var.instance_size
14+
node_count = region.electable_nodes
15+
disk_size_gb = local.disk_size
16+
}
17+
priority = region.priority
18+
read_only_specs = region.read_only_nodes == 0 ? null : {
19+
instance_size = var.instance_size
20+
node_count = region.read_only_nodes
21+
disk_size_gb = local.disk_size
22+
}
23+
auto_scaling = var.auto_scaling_disk_gb_enabled ? {
24+
disk_gb_enabled = true
25+
} : null
26+
}
27+
])
28+
}
29+
]
30+
]
31+
]
32+
)
33+
}
34+
35+
moved {
36+
from = mongodbatlas_cluster.this
37+
to = mongodbatlas_advanced_cluster.this
38+
}
39+
40+
41+
resource "mongodbatlas_advanced_cluster" "this" {
42+
lifecycle {
43+
precondition {
44+
condition = !(var.auto_scaling_disk_gb_enabled && var.disk_size > 0)
45+
error_message = "Must use either auto_scaling_disk_gb_enabled or disk_size, not both."
46+
}
47+
}
48+
49+
project_id = var.project_id
50+
name = var.cluster_name
51+
cluster_type = var.cluster_type
52+
mongo_db_major_version = var.mongo_db_major_version
53+
replication_specs = local.replication_specs
54+
tags = var.tags
55+
}
56+
57+
data "mongodbatlas_cluster" "this" { # note the usage of `cluster` not `advanced_cluster`, this is to have outputs stay compatible with the v1 module
58+
name = mongodbatlas_advanced_cluster.this.name
59+
project_id = mongodbatlas_advanced_cluster.this.project_id
60+
61+
depends_on = [mongodbatlas_advanced_cluster.this]
62+
}
63+
64+
# OLD cluster configuration:
65+
# resource "mongodbatlas_cluster" "this" {
66+
# lifecycle {
67+
# precondition {
68+
# condition = !(var.auto_scaling_disk_gb_enabled && var.disk_size > 0)
69+
# error_message = "Must use either auto_scaling_disk_gb_enabled or disk_size, not both."
70+
# }
71+
# }
72+
73+
# project_id = var.project_id
74+
# name = var.cluster_name
75+
76+
# auto_scaling_disk_gb_enabled = var.auto_scaling_disk_gb_enabled
77+
# cluster_type = var.cluster_type
78+
# disk_size_gb = var.disk_size
79+
# mongo_db_major_version = var.mongo_db_major_version
80+
# provider_instance_size_name = var.instance_size
81+
# provider_name = var.provider_name
82+
83+
# dynamic "tags" {
84+
# for_each = var.tags
85+
# content {
86+
# key = tags.key
87+
# value = tags.value
88+
# }
89+
# }
90+
91+
# dynamic "replication_specs" {
92+
# for_each = var.replication_specs
93+
# content {
94+
# num_shards = replication_specs.value.num_shards
95+
# zone_name = replication_specs.value.zone_name
96+
97+
# dynamic "regions_config" {
98+
# for_each = replication_specs.value.regions_config
99+
# content {
100+
# electable_nodes = regions_config.value.electable_nodes
101+
# priority = regions_config.value.priority
102+
# read_only_nodes = regions_config.value.read_only_nodes
103+
# region_name = regions_config.value.region_name
104+
# }
105+
# }
106+
# }
107+
# }
108+
# }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
output "mongodb_connection_strings" {
2+
value = mongodbatlas_advanced_cluster.this.connection_strings
3+
description = "This is the MongoDB Atlas connection strings. Note, these do not show the connection mechanism of the database details"
4+
}
5+
6+
output "cluster_name" {
7+
value = mongodbatlas_advanced_cluster.this.name
8+
description = "MongoDB Atlas cluster name"
9+
}
10+
11+
output "project_id" {
12+
value = mongodbatlas_advanced_cluster.this.project_id
13+
description = "MongoDB Atlas project id"
14+
}
15+
16+
output "replication_specs" {
17+
value = data.mongodbatlas_cluster.this.replication_specs
18+
description = "Replication Specs for cluster"
19+
}
20+
21+
output "mongodbatlas_cluster" {
22+
value = data.mongodbatlas_cluster.this
23+
description = "Full cluster configuration for mongodbatlas_cluster resource"
24+
}
25+
26+
output "mongodbatlas_advanced_cluster" {
27+
value = data.mongodbatlas_cluster.this
28+
description = "Full cluster configuration for mongodbatlas_advanced_cluster resource"
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
variable "project_id" {
2+
type = string
3+
}
4+
5+
variable "cluster_name" {
6+
type = string
7+
}
8+
9+
variable "cluster_type" {
10+
type = string
11+
}
12+
13+
variable "instance_size" {
14+
type = string
15+
}
16+
17+
variable "mongo_db_major_version" {
18+
type = string
19+
}
20+
21+
variable "provider_name" {
22+
type = string
23+
}
24+
25+
# OPTIONAL VARIABLES
26+
27+
variable "disk_size" {
28+
type = number
29+
default = 0
30+
}
31+
32+
variable "auto_scaling_disk_gb_enabled" {
33+
type = bool
34+
default = false
35+
}
36+
37+
variable "tags" {
38+
type = map(string)
39+
default = {}
40+
}
41+
42+
variable "replication_specs" {
43+
type = list(object({
44+
num_shards = number
45+
zone_name = string
46+
regions_config = set(object({
47+
region_name = string
48+
electable_nodes = number
49+
priority = number
50+
read_only_nodes = optional(number, 0)
51+
}))
52+
}))
53+
default = [{
54+
num_shards = 1
55+
zone_name = "Zone 1"
56+
regions_config = [{
57+
region_name = "US_EAST_1"
58+
electable_nodes = 3
59+
priority = 7
60+
}]
61+
}]
62+
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
terraform {
2-
required_version = ">= 1.0"
2+
required_providers {
3+
mongodbatlas = {
4+
source = "mongodb/mongodbatlas"
5+
version = "~> 1.26" # todo: PLACEHOLDER_TPF_RELEASE
6+
}
7+
}
8+
required_version = ">= 1.8" # Minimum moved block supported version
39
}

0 commit comments

Comments
 (0)