Skip to content

Latest commit

 

History

History

terraform

Folders and files

README.md

cloudemu terraform compatibility

Evidence that real Terraform / OpenTofu works against CloudEmu. This module (its own Go module, so terraform-exec stays out of the core) drives the real tofu/terraform binary through init → apply → plan → destroy against an in-process CloudEmu AWS wire server — no Docker, no subprocess.

The load-bearing assertion is the post-apply plan: it must report no changes. A change means a resource's read did not round-trip what apply wrote (a "perpetual diff") — the class of wire-fidelity bug this suite exists to catch. Each fixtures/<case>/ is one scenario.

Point your own Terraform at CloudEmu

The easy way is the cloudemu-tf wrapper in this directory. Run it in place of terraform/tofu; your config needs only an empty provider "aws" {} block:

cloudemu serve &                                   # AWS on :4566
CLOUDEMU_ENDPOINT=http://localhost:4566 ./cloudemu-tf init
./cloudemu-tf apply

It writes cloudemu_providers_override.tf with an endpoints {} key for every AWS service CloudEmu serves (the list is checked against server/aws/aws.go by wrapper_test.go), so no call goes to real AWS. Two CloudEmu services have no provider key and are not listed: Bedrock Agent Runtime and Savings Plans.

To write the provider block by hand, use:

provider "aws" {
  access_key = "test"
  secret_key = "test"
  region     = "us-east-1"

  s3_use_path_style           = true   # CloudEmu S3 is path-style
  skip_credentials_validation = true
  skip_metadata_api_check     = true

  endpoints {
    s3       = "http://localhost:4566"
    dynamodb = "http://localhost:4566"
    iam      = "http://localhost:4566"
    sts      = "http://localhost:4566"
    # ... one line per service you use
  }
}

Leave skip_requesting_account_id off. The provider then gets the account from STS GetCallerIdentity, which CloudEmu serves (000000000000 by default). With the flag on, ARNs the provider builds itself (for example the Redshift cluster ARN it tags by) get an empty account. Keep the sts endpoint set.

Run the suite

cd contrib/terraform
go test ./...        # needs a `tofu` (preferred) or `terraform` binary on PATH

The test prefers OpenTofu (MPL-2.0) and falls back to Terraform; the HCL is identical on both. It skips cleanly when neither binary is installed.

Verified vs. known limits

A green suite proves only what its fixtures exercise. What each fixture asserts is idempotent is verified; everything else is a known gap, closed as a fixture needs it:

  • DynamoDB — PAY_PER_REQUEST and PROVISIONED (with read/write_capacity) and global_secondary_index blocks round-trip.
  • Networking — aws_vpc, aws_subnet, aws_security_group, aws_route_table and aws_route_table_association round-trip. A new security group is seeded with the real allow-all egress default (AWS only — Azure/GCP/OCI network groups keep their own defaults); Revoke* is idempotent (Terraform strips IPv4 and IPv6 defaults, and a v4-only VPC has no IPv6 one). ENIs don't yet model security-group membership, so DeleteSecurityGroup has no in-use dependency check — fine until instances/ENIs enter a fixture.
  • S3 — the base aws_s3_bucket round-trips. The standalone config resources (aws_s3_bucket_policy, _public_access_block, _cors_configuration, _server_side_encryption_configuration, _lifecycle_configuration) are not persisted — their writes are accepted as a no-op, so a config-resource fixture would drift. GetBucketLocation always reports us-east-1.
  • Persist/restore — a persisted-then-restored DynamoDB table drops the describe-only fields (attributes, billing mode); irrelevant to a single apply→destroy flow.