Skip to content

doc: Implements v2 and v3 MongoDB Atlas cluster migration examples #3050

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4c15b16
initial v2 & v3 implementations
EspenAlbert Feb 11, 2025
b57ba7f
Adds `v2` Implementation Changes and Highlights
EspenAlbert Feb 11, 2025
6bda23f
doc: Update README with v3 implementation changes and highlights
EspenAlbert Feb 11, 2025
746389b
doc: Update README and add v1_v2 and v3 variable files for cluster mi…
EspenAlbert Feb 11, 2025
95cea88
style: fmt
EspenAlbert Feb 11, 2025
d9976bf
doc: Fix v3 usage of cluster data source
EspenAlbert Feb 11, 2025
46014a3
feat: Add output for MongoDB connection strings in v1, v2, and v3 exa…
EspenAlbert Feb 11, 2025
fd587fd
style: fmt
EspenAlbert Feb 11, 2025
51e1b37
chore: Enable Advanced Cluster V2 Schema validation in tf-validate sc…
EspenAlbert Feb 11, 2025
e73b91f
fix lint errors
EspenAlbert Feb 11, 2025
2f52072
feat: Add function to check for V2 schema directories in tf-validate …
EspenAlbert Feb 11, 2025
12265f3
refactor: Use approach 1 (file headings) rather than a table
EspenAlbert Feb 12, 2025
dee594f
address PR comments
EspenAlbert Feb 12, 2025
d5a9be7
docs: Update README files to enhance migration guidance for mongodbat…
EspenAlbert Feb 12, 2025
e199021
address PR comments
EspenAlbert Feb 12, 2025
12c71e7
chore: Update ToC
EspenAlbert Feb 12, 2025
bc06455
Review suggestions
EspenAlbert Feb 13, 2025
02e08a8
update var name
EspenAlbert Feb 13, 2025
dcbfbd6
docs: Enhance README to clarify standalone usage and upgrade support …
EspenAlbert Feb 13, 2025
720fc59
fix: Update required_version comment for clarity in versions.tf files
EspenAlbert Feb 13, 2025
c12fbdd
docs: Add v3_no_plan_changes and step for ensuring no changes when us…
EspenAlbert Feb 13, 2025
58de2de
update var name to make tf-validate pass
EspenAlbert Feb 13, 2025
ccbaa97
Merge branch 'CLOUDP-274025-dev-docs-examples' into CLOUDP-299312_dem…
EspenAlbert Feb 13, 2025
f2d1520
fix: Update environment variable for Terraform validation in v2 schema
EspenAlbert Feb 13, 2025
fef8a10
docs: Update ToC
EspenAlbert Feb 13, 2025
9b1e065
fix: update restore command in tf-validate script to use preview_prov…
EspenAlbert Feb 13, 2025
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
Original file line number Diff line number Diff line change
@@ -1,13 +1,140 @@
# Module Maintainer - `mongodbatlas_cluster` to `mongodbatlas_advanced_cluster`

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

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

<!-- TODO: Add highlights of module implementations -->
The rest of this document summarizes the different implementations:

- [Step 1: Module `v1` Implementation Summary](#step-1-module-v1-implementation-summary)
- [`variables.tf`](#variablestf)
- [`main.tf`](#maintf)
- [`outputs.tf`](#outputstf)
- [Step 2: Module `v2` Implementation Changes and Highlights](#step-2-module-v2-implementation-changes-and-highlights)
- [`variables.tf` unchanged from `v1`](#variablestf-unchanged-from-v1)
- [`versions.tf`](#versionstf)
- [`main.tf`](#maintf-1)
- [`outputs.tf`](#outputstf-1)
- [Step 3: Module `v3` Implementation Changes and Highlights](#step-3-module-v3-implementation-changes-and-highlights)
- [`variables.tf`](#variablestf-1)
- [`main.tf`](#maintf-2)
- [`outputs.tf`](#outputstf-2)


## Step 1: Module `v1` Implementation Summary
This module creates a `mongodbatlas_cluster`

### [`variables.tf`](v1/variables.tf)
An abstraction for the `mongodbatlas_cluster` resource:
- Not all arguments are exposed, but the arguments follow the schema closely
- `disk_size` and `auto_scaling_disk_gb_enabled` are mutually exclusive and validated in the `precondition` in `main.tf`

### [`main.tf`](v1/main.tf)
It uses `dynamic` blocks to represent:
- `tags`
- `replication_specs`
- `regions_config` (nested inside replication_specs)

### [`outputs.tf`](v1/outputs.tf)
- Expose some attributes of `mongodbatlas_cluster` but also the full resource with `mongodbatlas_cluster` output variable:
```terraform
output "mongodbatlas_cluster" {
value = mongodbatlas_cluster.this
description = "Full cluster configuration for mongodbatlas_cluster resource"
}
```

## Step 2: Module `v2` Implementation Changes and Highlights
This module uses HCL code to create a `mongodbatlas_advanced_cluster` resource that is compatible with the input variables of `v1`.
The module supports standalone usage when there is no existing `mongodbatlas_cluster` and also upgrading from `v1` using a `moved` block.

### [`variables.tf`](v2/variables.tf) unchanged from `v1`
### [`versions.tf`](v2/versions.tf)
- `required_version` of Terraform CLI bumped to `# todo: minimum moved block supported version` for `moved` block support
- `mongodbatlas.version` bumped to `# todo: PLACEHOLDER_TPF_RELEASE` for new `mongodbatlas_advanced_cluster` v2 schema support

### [`main.tf`](v2/main.tf)
<!-- TODO: Update link to (schema v2) docs page -->
- `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`
- We use the Terraform builtin [range](https://developer.hashicorp.com/terraform/language/functions/range) function (`range(old_spec.num_shards)`) to flatten `num_shards`
- We expand `read_only_specs` and `electable_specs` into nested attributes
- We use the `var.provider_name` in the `region_configs.*.instance_size`
- `moved` block:
```terraform
moved {
from = mongodbatlas_cluster.this
to = mongodbatlas_advanced_cluster.this
}
```
- `resource "mongodbatlas_advanced_cluster" "this"`
- We reference the `local.replication_specs` as input to `replication_specs` (`replication_specs = local.replication_specs`)
- Tags can be passed directly instead of the dynamic block (`tags = var.tags`)
- Adds `data "mongodbatlas_cluster" "this"` to avoid breaking changes in `outputs.tf` (see below)

### [`outputs.tf`](v2/outputs.tf)
- All outputs can use `mongodbatlas_advanced_cluster` except for
- `replication_specs`, we use the `data.mongodbatlas_cluster.this.replication_specs` to keep the same format
- `mongodbatlas_cluster`, we use the `data.mongodbatlas_cluster.this` to keep the same format


## Step 3: Module `v3` Implementation Changes and Highlights
This module adds variables to support the latest `mongodbatlas_advanced_cluster` features while staying compatible with the old input variables.
The module supports standalone usage when there is no existing `mongodbatlas_cluster` and also upgrading from `v1` using a `moved` block.
The module also supports changing an existing `mongodbatlas_advanced_cluster` created in `v2`.

### [`variables.tf`](v3/variables.tf)
- Add `replication_specs_new`, this is almost a full mirror of the `replication_specs` of the latest `mongodbatlas_advanced_cluster` schema
- Use a `[]` for default to allow continued usage of the old `replication_specs`
- Usage of `optional` to simplify the caller
- Add `default` to `instance_size` and `provider_name` as these are not required when `replication_specs_new` is used
- Change `[]` default to `replication_specs` to allow usage of `replication_specs_new`

### [`main.tf`](v3/main.tf)
- Add *defaults* to old variables in `locals`:
- `old_disk_size`
- `old_instance_size`
- `old_provider_name`
- Add `_old` suffix to `locals.replication_specs` to make conditional code (see below) more readable
- Add `precondition` for `replication_specs` to validate only `var.replication_specs_new` or `replication_specs` is used
```terraform
precondition {
condition = !((local.use_new_replication_specs && length(var.replication_specs) > 0) || (!local.use_new_replication_specs && length(var.replication_specs) == 0))
error_message = "Must use either replication_specs_new or replication_specs, not both."
}
```
- Use a conditional for`replication_specs` in `resource "mongodbatlas_advanced_cluster" "this"`:
```terraform
# other attributes...
replication_specs = local.use_new_replication_specs ? var.replication_specs_new : local.replication_specs_old
tags = var.tags
}
```
- Use `count` for data source to avoid error when Asymmetric Shards are used:
```terraform
data "mongodbatlas_cluster" "this" {
count = local.use_new_replication_specs ? 0 : 1 # Not safe when Asymmetric Shards are used
name = mongodbatlas_advanced_cluster.this.name
project_id = mongodbatlas_advanced_cluster.this.project_id

depends_on = [mongodbatlas_advanced_cluster.this]
}
```

### [`outputs.tf`](v3/outputs.tf)
- Update `replication_specs` and `mongodbatlas_cluster` to handle the case when the new schema is used:
```terraform
output "replication_specs" {
value = local.use_new_replication_specs ? [] : data.mongodbatlas_cluster.this[0].replication_specs # updated
description = "Replication Specs for cluster, will be empty if var.replication_specs_new is set"
}

output "mongodbatlas_cluster" {
value = local.use_new_replication_specs ? null : data.mongodbatlas_cluster.this[0] # updated
description = "Full cluster configuration for mongodbatlas_cluster resource, will be null if var.replication_specs_new is set"
}
```
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
output "mongodb_raw_connection_strings" {
output "mongodb_connection_strings" {
value = mongodbatlas_cluster.this.connection_strings
description = "This is the raw list of MongoDB Atlas connection strings. Note, these do not show the connection mechanism of the database details"
description = "These are the MongoDB Atlas connection strings. Note that these do not show the connection mechanism of the database details"
}

output "cluster_name" {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
locals {
disk_size = var.auto_scaling_disk_gb_enabled ? null : var.disk_size
replication_specs = flatten([
for old_spec in var.replication_specs : [
for shard in range(old_spec.num_shards) : [
{
zone_name = old_spec.zone_name
region_configs = tolist([
for region in old_spec.regions_config : {
region_name = region.region_name
provider_name = var.provider_name
electable_specs = {
instance_size = var.instance_size
node_count = region.electable_nodes
disk_size_gb = local.disk_size
}
priority = region.priority
read_only_specs = region.read_only_nodes == 0 ? null : {
instance_size = var.instance_size
node_count = region.read_only_nodes
disk_size_gb = local.disk_size
}
auto_scaling = var.auto_scaling_disk_gb_enabled ? {
disk_gb_enabled = true
} : null
}
])
}
]
]
]
)
}

moved {
from = mongodbatlas_cluster.this
to = mongodbatlas_advanced_cluster.this
}


resource "mongodbatlas_advanced_cluster" "this" {
lifecycle {
precondition {
condition = !(var.auto_scaling_disk_gb_enabled && var.disk_size > 0)
error_message = "Must use either auto_scaling_disk_gb_enabled or disk_size, not both."
}
}

project_id = var.project_id
name = var.cluster_name
cluster_type = var.cluster_type
mongo_db_major_version = var.mongo_db_major_version
replication_specs = local.replication_specs
tags = var.tags
}

data "mongodbatlas_cluster" "this" { # note the usage of `cluster` not `advanced_cluster`, this is to have outputs stay compatible with the v1 module
name = mongodbatlas_advanced_cluster.this.name
project_id = mongodbatlas_advanced_cluster.this.project_id

depends_on = [mongodbatlas_advanced_cluster.this]
}

# OLD cluster configuration:
# resource "mongodbatlas_cluster" "this" {
# lifecycle {
# precondition {
# condition = !(var.auto_scaling_disk_gb_enabled && var.disk_size > 0)
# error_message = "Must use either auto_scaling_disk_gb_enabled or disk_size, not both."
# }
# }

# project_id = var.project_id
# name = var.cluster_name

# auto_scaling_disk_gb_enabled = var.auto_scaling_disk_gb_enabled
# cluster_type = var.cluster_type
# disk_size_gb = var.disk_size
# mongo_db_major_version = var.mongo_db_major_version
# provider_instance_size_name = var.instance_size
# provider_name = var.provider_name

# dynamic "tags" {
# for_each = var.tags
# content {
# key = tags.key
# value = tags.value
# }
# }

# dynamic "replication_specs" {
# for_each = var.replication_specs
# content {
# num_shards = replication_specs.value.num_shards
# zone_name = replication_specs.value.zone_name

# dynamic "regions_config" {
# for_each = replication_specs.value.regions_config
# content {
# electable_nodes = regions_config.value.electable_nodes
# priority = regions_config.value.priority
# read_only_nodes = regions_config.value.read_only_nodes
# region_name = regions_config.value.region_name
# }
# }
# }
# }
# }
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
output "mongodb_connection_strings" {
value = mongodbatlas_advanced_cluster.this.connection_strings
description = "This is the MongoDB Atlas connection strings. Note, these do not show the connection mechanism of the database details"
}

output "cluster_name" {
value = mongodbatlas_advanced_cluster.this.name
description = "MongoDB Atlas cluster name"
}

output "project_id" {
value = mongodbatlas_advanced_cluster.this.project_id
description = "MongoDB Atlas project id"
}

output "replication_specs" {
value = data.mongodbatlas_cluster.this.replication_specs
description = "Replication Specs for cluster"
}

output "mongodbatlas_cluster" {
value = data.mongodbatlas_cluster.this
description = "Full cluster configuration for mongodbatlas_cluster resource"
}

output "mongodbatlas_advanced_cluster" {
value = data.mongodbatlas_cluster.this
description = "Full cluster configuration for mongodbatlas_advanced_cluster resource"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
variable "project_id" {
type = string
}

variable "cluster_name" {
type = string
}

variable "cluster_type" {
type = string
}

variable "instance_size" {
type = string
}

variable "mongo_db_major_version" {
type = string
}

variable "provider_name" {
type = string
}

# OPTIONAL VARIABLES

variable "disk_size" {
type = number
default = 0
}

variable "auto_scaling_disk_gb_enabled" {
type = bool
default = false
}

variable "tags" {
type = map(string)
default = {}
}

variable "replication_specs" {
type = list(object({
num_shards = number
zone_name = string
regions_config = set(object({
region_name = string
electable_nodes = number
priority = number
read_only_nodes = optional(number, 0)
}))
}))
default = [{
num_shards = 1
zone_name = "Zone 1"
regions_config = [{
region_name = "US_EAST_1"
electable_nodes = 3
priority = 7
}]
}]
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
terraform {
required_version = ">= 1.0"
required_providers {
mongodbatlas = {
source = "mongodb/mongodbatlas"
version = "~> 1.26" # todo: PLACEHOLDER_TPF_RELEASE
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about using 1.27 as in the other examples I did, so we can search/replace easily if needed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I think it is better to change it later, otherwise CI will fail:

│ Error: Failed to query available provider packages
│ 
│ Could not retrieve the list of available versions for provider mongodb/mongodbatlas: no available releases match the given constraints ~> 1.26, ~> 1.27
│ 
│ To see which modules are currently depending on mongodb/mongodbatlas and what versions are specified, run the following command:
│     terraform providers

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

created a ticket so we don't forget: CLOUDP-300537

}
}
required_version = ">= 1.8" # Minimum moved block supported version
}
Loading