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.
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 applyIt 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.
cd contrib/terraform
go test ./... # needs a `tofu` (preferred) or `terraform` binary on PATHThe 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.
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_REQUESTandPROVISIONED(withread/write_capacity) andglobal_secondary_indexblocks round-trip. - Networking —
aws_vpc,aws_subnet,aws_security_group,aws_route_tableandaws_route_table_associationround-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, soDeleteSecurityGrouphas no in-use dependency check — fine until instances/ENIs enter a fixture. - S3 — the base
aws_s3_bucketround-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.GetBucketLocationalways reportsus-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.