From c1c9bd74f3245f592636155dfee620f188dc981c Mon Sep 17 00:00:00 2001 From: Noah White Date: Sun, 15 Mar 2026 02:25:06 +0000 Subject: [PATCH 1/2] feat(GHO-109): upgrade Vultr provider to v2.28.1, remove block storage workaround - Upgrade vultr/vultr provider from 2.22.1 to 2.28.1 in all modules and envs/dev - Remove null_resource attach_block_storage workaround (bug #660 fixed in 2.28.1) - Remove lifecycle { ignore_changes = [attached_to_instance] } from vultr_block_storage - Remove null provider dependency from block_storage module - Set attached_to_instance directly on vultr_block_storage (native provider support) - Add vultr-block-storage unit tests - Update .terraform.lock.hcl with 2.28.1 provider hashes --- opentofu/envs/dev/.terraform.lock.hcl | 79 ++++++++++++---- opentofu/envs/dev/main.tofu | 2 +- .../tests/vultr-block-storage.tofutest.hcl | 81 +++++++++++++++++ .../modules/vultr/block_storage/main.tofu | 91 ++----------------- opentofu/modules/vultr/firewall/main.tofu | 2 +- opentofu/modules/vultr/instance/main.tofu | 2 +- 6 files changed, 150 insertions(+), 107 deletions(-) create mode 100644 opentofu/envs/dev/tests/vultr-block-storage.tofutest.hcl diff --git a/opentofu/envs/dev/.terraform.lock.hcl b/opentofu/envs/dev/.terraform.lock.hcl index 2baac79..a956038 100644 --- a/opentofu/envs/dev/.terraform.lock.hcl +++ b/opentofu/envs/dev/.terraform.lock.hcl @@ -66,6 +66,47 @@ provider "registry.opentofu.org/grafana/grafana" { ] } +provider "registry.opentofu.org/hashicorp/null" { + version = "3.2.4" + constraints = "~> 3.2" + hashes = [ + "h1:jsKjBiLb+v3OIC3xuDiY4sR0r1OHUMSWPYKult9MhT0=", + "zh:1769783386610bed8bb1e861a119fe25058be41895e3996d9216dd6bb8a7aee3", + "zh:32c62a9387ad0b861b5262b41c5e9ed6e940eda729c2a0e58100e6629af27ddb", + "zh:339bf8c2f9733fce068eb6d5612701144c752425cebeafab36563a16be460fb2", + "zh:36731f23343aee12a7e078067a98644c0126714c4fe9ac930eecb0f2361788c4", + "zh:3d106c7e32a929e2843f732625a582e562ff09120021e510a51a6f5d01175b8d", + "zh:74bcb3567708171ad83b234b92c9d63ab441ef882b770b0210c2b14fdbe3b1b6", + "zh:90b55bdbffa35df9204282251059e62c178b0ac7035958b93a647839643c0072", + "zh:ae24c0e5adc692b8f94cb23a000f91a316070fdc19418578dcf2134ff57cf447", + "zh:b5c10d4ad860c4c21273203d1de6d2f0286845edf1c64319fa2362df526b5f58", + "zh:e05bbd88e82e1d6234988c85db62fd66f11502645838fff594a2ec25352ecd80", + ] +} + +provider "registry.opentofu.org/infisical/infisical" { + version = "0.16.7" + constraints = "~> 0.16" + hashes = [ + "h1:J8dXUHMbjRly7zj4FR6YCrn/eBZBDWmBW+UYsKzh/Ow=", + "zh:0272fa798147cf1eb2c5545f1ecfdb554d9651fdc611a9787598a8d210822321", + "zh:0ed1eca6e9d8229f007fe09f743eb38a5df4ee7c16b5e48dc1da9a9ae70680b6", + "zh:1d4c1273709762e55071f15e5588e88eb18f1b0e826f3d338445dcd511720cd2", + "zh:3de789dc066d9574c4eff02c06ad849229873d99ed08dab4d5586ed953f8f3c2", + "zh:436d869fc5bb09bbe1223b2173e435d749d0323da684ed5c3aaa7584d4da6d38", + "zh:6a62a490f3cae07ad8939d84a7f2385aa7c979ea60e75356109da19c83a6ce81", + "zh:6d5a1d0b8b34f9d277b63078eceb8f7c788b7ddf7f4327d115a8157441bd4e22", + "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", + "zh:a208c191a992391d343f6caa41c4700ae2c1fa348d54d05e6fca5b8329c385ad", + "zh:aa09a54796eafacc77f55650374a59e23556ddf549c7066f4d4128a031687b61", + "zh:c06d33618cf4560e73919258044532660706804e0d715a8b061c6d8ac1db3ecf", + "zh:c43bb011505aca5015b7121d1f01bd872ab1cc60745bb23e6bc718edb0967f23", + "zh:d61747595a3b4fc9228b69220aee7de6b08515c1ce78913b7d6937ff88e0c85b", + "zh:df73a9acfb16f60c9e1e50386d29d9571f7c357ee7a1a14f2c98bb603caf9c34", + "zh:dfa82fe17653b6545a5489b69e8dfd9897199d07b7c27eb8e9ce6ccf1b85f7c2", + ] +} + provider "registry.opentofu.org/pagerduty/pagerduty" { version = "3.31.4" constraints = "~> 3.31.0" @@ -152,25 +193,25 @@ provider "registry.opentofu.org/tailscale/tailscale" { } provider "registry.opentofu.org/vultr/vultr" { - version = "2.22.1" - constraints = "2.22.1" + version = "2.28.1" + constraints = "2.28.1" hashes = [ - "h1:dQNGsMCsdD6/tPqU/CqZGy5EV8wabS2O+LhQwQw1r1g=", - "zh:0084f6bebcc730bb9cd825be4808071032a44300e32b69cfccb93d9838f9de8d", - "zh:0085352778803cacb1402df41311b63cc18717a946d425cd62d7a49451ef51ac", - "zh:1555e918061588da8de21e8e65d756ebc05b9b4d175786f1c84adca4dece50a7", - "zh:1e75379ab63e704cc80e421e29a5260230a2f56fcce228385a4b79b05b320769", - "zh:303f768e91c8d92bb902e93822f0d93c07dc5b560265be0655a871ed16106398", - "zh:33edb0428ee02ddb6ade33bc8054ba82f63924ccf17bb12c1a81bc05900cd047", - "zh:35e23877b11c98b0f11894c6b354675cc640d0bbcca554c68ca1c1480cb0f85d", - "zh:3b669587c0ecdbd0e7c8fb29a1e5cb3d0d28ee5016376c66493ae71d45ef6946", - "zh:3dc39667b73e9930c56ccfec80f970d719c10d779e03b467f7015dadf5a14159", - "zh:6c5bdf851987cbc0e0901c3e83b2d2b273ed065030d22793eed78d20cae824c0", - "zh:82dbeb59c262a038ea56a04ee1d2cd2e7679a351d29d5e6a5d38f6cc9f42856c", - "zh:a5ac61a9885f941030038f1bd3b962411955ca0cc33bc90343bdfd543957fd8a", - "zh:ca11c5150121faabec2d9f29aa96bdd319ca36aa0cde67cd3fbb50f560f05c05", - "zh:dabf0dcab52809691d56b423d176169e0a5bcb7a700754bf0e59d05d4ebd9d2f", - "zh:e133da592117283ae4b24cd5aec979cedb3de5b5953ca3787dae0d3f62c4e706", - "zh:ea6e58525b0fc247c894eae1a4dfc3064a489a886530893b5704d0a55cb77750", + "h1:DeA8pSJHHzGaQzaLT/XEcrOx+DXlgXrEwz8TibmHQLE=", + "zh:10bc56e26f6aa68b71e99c02b1d3de3677e30a2172f5a7aee66434720b304d31", + "zh:3360e23e91210a69e08f7fc678bdfc47229839c45dffd4df6609985e111f85bf", + "zh:3b61ab446628a11bc6d29476aba77553d39bc07b5e47a565368c1f6c325657ce", + "zh:3cd7d0035fcbd6a1ff07640bb34d3f5d45f0e8c4616e1306f52ad28ce20c3ac0", + "zh:3eed1bc4578bdd61e26081588ca1ecbe5379b6e532af0cc2c864879fdac78ded", + "zh:400c4fa37871196bf9209891a760b5a7862857255efcf6092f354142e96fc438", + "zh:52e784bd102c07f022c52748b0a3ff1e0e5ee2f54a75d8dfe0b49ede21f17ca1", + "zh:53dab88aa191a1eaa3fc759ddddc6d112fadec476ccf5a5ba898ac93478da7b8", + "zh:54727a0f5a1fd21735f090e8fca7e7509838a487659a609e1469be294aaffdf2", + "zh:5c11e5120a6b7b41736cce3299a2e5d5b9a5e6f1d748d350690cc945d9b6f9b4", + "zh:7669c9e958f9a2ab79af3b94c33e3ac8008d93734a285c09ed8b273e79d62dbc", + "zh:785fb6ff8b96fb99a4bd9727782a15a2376d4637f11f0a1d8ee40e7684003616", + "zh:8a6d88fd422a417e15dc52ec40baa4f22661e4bb93e4dedf857f86dc9a8e6dd0", + "zh:ca3e6837c6b8de1506b1326cac332b6196035c4a8be8d05c21a21a29234a9394", + "zh:d87ceaf14748da69384e1e8f5b68d4cac165b69d3012f4e917a018069e1ea843", + "zh:f67f8fc190add35dbb890b2a602465f69abde6f3c5780831f3c8811394cd1ac3", ] } diff --git a/opentofu/envs/dev/main.tofu b/opentofu/envs/dev/main.tofu index 0ad5350..18f6c8f 100644 --- a/opentofu/envs/dev/main.tofu +++ b/opentofu/envs/dev/main.tofu @@ -4,7 +4,7 @@ terraform { required_providers { vultr = { source = "vultr/vultr" - version = "= 2.22.1" + version = "= 2.28.1" } cloudflare = { source = "cloudflare/cloudflare" diff --git a/opentofu/envs/dev/tests/vultr-block-storage.tofutest.hcl b/opentofu/envs/dev/tests/vultr-block-storage.tofutest.hcl new file mode 100644 index 0000000..bb08562 --- /dev/null +++ b/opentofu/envs/dev/tests/vultr-block-storage.tofutest.hcl @@ -0,0 +1,81 @@ +mock_provider "vultr" { + mock_resource "vultr_block_storage" { + defaults = { + id = "test-block-storage-id" + mount_id = "test-instance-id" + } + } +} + +run "block_storage_defaults" { + command = plan + + module { + source = "../../modules/vultr/block_storage" + } + + variables { + region = "ewr" + mount_instance_id = "test-instance-id" + } + + assert { + condition = vultr_block_storage.this.region == "ewr" + error_message = "Block storage region should match provided value" + } + + assert { + condition = vultr_block_storage.this.size_gb == 25 + error_message = "Block storage size should default to 25 GB" + } + + assert { + condition = vultr_block_storage.this.label == "ghost-block" + error_message = "Block storage label should default to ghost-block" + } + + assert { + condition = vultr_block_storage.this.live == true + error_message = "Block storage live should be true" + } + + assert { + condition = vultr_block_storage.this.attached_to_instance == "test-instance-id" + error_message = "Block storage should be attached to the provided instance ID" + } +} + +run "block_storage_custom_values" { + command = plan + + module { + source = "../../modules/vultr/block_storage" + } + + variables { + region = "lax" + size_gb = 50 + label = "custom-label" + mount_instance_id = "custom-instance-id" + } + + assert { + condition = vultr_block_storage.this.region == "lax" + error_message = "Block storage region should match provided value" + } + + assert { + condition = vultr_block_storage.this.size_gb == 50 + error_message = "Block storage size should match provided value" + } + + assert { + condition = vultr_block_storage.this.label == "custom-label" + error_message = "Block storage label should match provided value" + } + + assert { + condition = vultr_block_storage.this.attached_to_instance == "custom-instance-id" + error_message = "Block storage should be attached to the provided instance ID" + } +} diff --git a/opentofu/modules/vultr/block_storage/main.tofu b/opentofu/modules/vultr/block_storage/main.tofu index bf2b596..c572dfb 100644 --- a/opentofu/modules/vultr/block_storage/main.tofu +++ b/opentofu/modules/vultr/block_storage/main.tofu @@ -2,94 +2,15 @@ terraform { required_providers { vultr = { source = "vultr/vultr" - version = "= 2.22.1" - } - null = { - source = "hashicorp/null" - version = "~> 3.0" + version = "= 2.28.1" } } } -# Block storage resource - attachment is handled separately via null_resource -# due to Vultr provider bug: https://github.com/vultr/terraform-provider-vultr/issues/660 resource "vultr_block_storage" "this" { - region = var.region - size_gb = var.size_gb - label = var.label - live = true - - # Ignore attachment changes - managed by null_resource below to work around - # provider bug. This prevents Terraform from trying to detach/reattach. - lifecycle { - ignore_changes = [attached_to_instance] - } -} - -# Workaround for Vultr provider bug #660 -# The provider fails to attach block storage to recreated instances with: -# Error: error getting block storage: {"error":"Nothing to change","status":400} -# -# This null_resource uses the Vultr CLI to handle attachment. -# It inherits VULTR_API_KEY from the environment (set via TF_VAR_vultr_api_key in CI). -resource "null_resource" "attach_block_storage" { - # Note: count removed because it cannot depend on values unknown at plan time - # (instance_id is unknown when instance is being recreated) - - triggers = { - instance_id = var.mount_instance_id - block_storage_id = vultr_block_storage.this.id - } - - provisioner "local-exec" { - command = <<-EOT - set -e - - # API key is inherited from environment (VULTR_API_KEY or TF_VAR_vultr_api_key) - export VULTR_API_KEY="$${VULTR_API_KEY:-$TF_VAR_vultr_api_key}" - if [ -z "$VULTR_API_KEY" ]; then - echo "ERROR: VULTR_API_KEY or TF_VAR_vultr_api_key must be set" - exit 1 - fi - - BLOCK_STORAGE_ID="${vultr_block_storage.this.id}" - INSTANCE_ID="${var.mount_instance_id}" - - echo "Waiting for instance to be fully ready..." - sleep 30 - - echo "Checking current block storage state..." - CURRENT_ATTACHED=$(vultr-cli block-storage get "$BLOCK_STORAGE_ID" | tail -n1 | awk '{print $3}') - echo "Currently attached to: $${CURRENT_ATTACHED:-none}" - - # If attached to something (possibly stale/deleted instance), detach first - if [ -n "$CURRENT_ATTACHED" ] && [ "$CURRENT_ATTACHED" != "$INSTANCE_ID" ]; then - echo "Detaching from previous instance..." - vultr-cli block-storage detach "$BLOCK_STORAGE_ID" --live || true - - echo "Waiting for detach to complete..." - sleep 15 - fi - - # Attach to the new instance - echo "Attaching block storage to instance $INSTANCE_ID..." - vultr-cli block-storage attach "$BLOCK_STORAGE_ID" --instance="$INSTANCE_ID" --live || true - - # Verify attachment - echo "Verifying block storage attachment..." - sleep 5 - VERIFIED_ATTACHED=$(vultr-cli block-storage get "$BLOCK_STORAGE_ID" | tail -n1 | awk '{print $3}') - - if [ "$VERIFIED_ATTACHED" = "$INSTANCE_ID" ]; then - echo "Verification successful: block storage is attached to $INSTANCE_ID" - else - echo "WARNING: Block storage may not be properly attached" - echo "Expected: $INSTANCE_ID" - echo "Actual: $${VERIFIED_ATTACHED:-none}" - exit 1 - fi - EOT - } - - depends_on = [vultr_block_storage.this] + region = var.region + size_gb = var.size_gb + label = var.label + live = true + attached_to_instance = var.mount_instance_id } diff --git a/opentofu/modules/vultr/firewall/main.tofu b/opentofu/modules/vultr/firewall/main.tofu index 146a45c..bf65d87 100644 --- a/opentofu/modules/vultr/firewall/main.tofu +++ b/opentofu/modules/vultr/firewall/main.tofu @@ -2,7 +2,7 @@ terraform { required_providers { vultr = { source = "vultr/vultr" - version = "= 2.22.1" + version = "= 2.28.1" } } } diff --git a/opentofu/modules/vultr/instance/main.tofu b/opentofu/modules/vultr/instance/main.tofu index 7581a37..8f9d073 100644 --- a/opentofu/modules/vultr/instance/main.tofu +++ b/opentofu/modules/vultr/instance/main.tofu @@ -1,6 +1,6 @@ terraform { required_providers { - vultr = { source = "vultr/vultr", version = "= 2.22.1" } + vultr = { source = "vultr/vultr", version = "= 2.28.1" } ct = { source = "poseidon/ct" version = "0.14.0" From 7fa475436b7303a235315ed4c8bdcf5ac7c67bb9 Mon Sep 17 00:00:00 2001 From: Noah White Date: Sun, 15 Mar 2026 02:33:52 +0000 Subject: [PATCH 2/2] test(GHO-109): trigger instance rebuild to verify native block storage attachment --- opentofu/modules/vultr/instance/userdata/ghost-compose.service | 1 + 1 file changed, 1 insertion(+) diff --git a/opentofu/modules/vultr/instance/userdata/ghost-compose.service b/opentofu/modules/vultr/instance/userdata/ghost-compose.service index 6d74b2d..e620e29 100644 --- a/opentofu/modules/vultr/instance/userdata/ghost-compose.service +++ b/opentofu/modules/vultr/instance/userdata/ghost-compose.service @@ -1,3 +1,4 @@ +# GHO-109: trigger rebuild to verify block storage attachment with native provider support [Unit] Description=Docker-Compose Ghost CMS Container After=docker.service var-mnt-storage.mount tinybird-provision.service infisical-secrets.service