Terraform and OpenTofu apply against feint with
no cloud account, on all three emulated providers. You change one
configuration line, the rest is the code you will deploy in production, and a full
apply then destroy cycle passes without creating a single billed resource. This
page gives the exact provider blocks for Scaleway, Outscale and
Exoscale, and covers the root volume case that blocked Scaleway
configurations for a long time.
What you will get
By the end of this page, an apply will have created your resources on all
three providers, a second plan will be empty everywhere, and a
destroy will have cleaned everything up, with no cloud account involved.
- Redirect the Scaleway provider with a single setting,
api_url. - Drive Outscale with its
apiblock, whose full path is a trap. - Wire up Exoscale through an environment variable, with a provider 0.71.0 or newer.
- Write a
root_volumethe provider finally accepts.
All three providers are now driven by Terraform against the emulator. Exoscale joined the other two with version 0.71.0 of its provider; configurations written for an older one need a single extra line, and the Exoscale section says which. Each case is covered below.
Terraform on Scaleway
The Scaleway provider is redirected with a single setting, api_url. The rest
of the configuration stays what you would write for the real provider, which is the
whole point: you test the code you will deploy.
terraform { required_providers { scaleway = { source = "scaleway/scaleway" version = "~> 2.79" } }}
provider "scaleway" { access_key = "SCWXXXXXXXXXXXXXXXXX" secret_key = "11111111-1111-1111-1111-111111111111" project_id = "11111111-1111-1111-1111-111111111111" organization_id = "99999999-9999-4999-8999-999999999999" region = "fr-par" zone = "fr-par-1"
api_url = "http://127.0.0.1:4599"}
resource "scaleway_vpc" "lab" { name = "lab-feint"}
resource "scaleway_vpc_private_network" "lab" { name = "internal-network" vpc_id = scaleway_vpc.lab.id}
resource "scaleway_instance_server" "web" { name = "web-terraform" type = "DEV1-S" image = "ubuntu_jammy"}Applying it needs no special adaptation:
terraform initterraform apply -auto-approveApply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
private_network_id = "fr-par/a0fa6f82-2aad-4f82-985a-2abeb65f05f7"server_id = "fr-par-1/d2303f04-8ab3-4e71-a682-ad91c81286b7"The identifiers returned carry the region or zone prefix, as on Scaleway. A
terraform destroy works symmetrically, which lets you validate a complete
cycle in CI without creating a single billed resource. For writing the
configurations themselves, the site's
Terraform section covers the
subject in depth.
The root volume, and the type that finally plans
The root_volume block accepts volume_type = "sbs_volume", and that unblocks
a real problem. For a long time this field had no usable value: the provider
refuses b_ssd outright from 2.79 on, and sbs_volume planned forever without
ever applying.
resource "scaleway_instance_server" "web" { name = "web-block" type = "DEV1-S" image = "ubuntu_jammy"
root_volume { volume_type = "sbs_volume" size_in_gb = 20 }}The disk is created in Block Storage, and the provider reads it back through
the fallback it has always used: instance.GetVolume first, then
block.GetVolume on a typed 404. The apply completes and the output does
return sbs_volume.
Terraform on Outscale
The official outscale/outscale provider drives the emulator end to end, with
an api block instead of Scaleway's api_url argument. The difference in form
matters: here the address carries the API's full path, version segment
included.
terraform { required_providers { outscale = { source = "outscale/outscale" version = "~> 1.7" } }}
provider "outscale" { access_key_id = "AAAAAAAAAAAAAAAAAAAA" secret_key_id = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
api { endpoint = "http://127.0.0.1:4599/api/v1" region = "eu-west-2" }}
resource "outscale_net" "lab" { ip_range = "10.70.0.0/16"
tags { key = "name" value = "lab-feint" }}
resource "outscale_subnet" "lab" { net_id = outscale_net.lab.net_id ip_range = "10.70.1.0/24"}
resource "outscale_vm" "web" { image_id = "ami-12345678" vm_type = "tinav4.c2r2p1" subnet_id = outscale_subnet.lab.subnet_id}The full cycle passes, including the empty second plan that is the real proof of correct emulation: if the API returns its resources differently from how it accepted them, Terraform proposes a phantom change on every run.
outscale_net.lab: Creation complete after 0s [id=vpc-4e93695a]outscale_subnet.lab: Creation complete after 1s [id=subnet-348a9987]outscale_vm.web: Creation complete after 0s [id=i-ef8f6fe1]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.No changes. Your infrastructure matches the configuration.Terraform on Exoscale
The official exoscale/exoscale provider drives the emulator from version
0.71.0 onwards. That was not always the case, and the history is worth knowing
because it explains the guard that remains. Until that version the provider built
two clients and only one honoured EXOSCALE_API_ENDPOINT: half of the calls
left for the real cloud with whatever credentials the environment held, and an
apply ended up split between the emulator and a billed account. A feint down run in that directory sent five signed requests to Exoscale's servers. The
fix came from upstream, in version 0.71.0 of the
provider.
The endpoint travels in an environment variable here, never in a provider
block argument: Exoscale declares none. The /v2 segment belongs to the
value, and leaving it out is the mistake that costs the most time.
export EXOSCALE_API_ENDPOINT='http://127.0.0.1:4599/v2'export EXOSCALE_API_KEY='EXOxxxxxxxxxxxxxxxxxxxx'export EXOSCALE_API_SECRET='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'export EXOSCALE_ZONE='ch-dk-2'Those four lines are exactly what feint env exoscale prints, which saves you
writing them.
terraform { required_providers { exoscale = { source = "exoscale/exoscale" version = ">= 0.71.0" } }}The version floor is enforced, not merely recommended. A provider older than
0.71.0 is recognised by its user agent and refused with a 400 that names the
version to pin, rather than serving half an apply.
{"message":"the Exoscale Terraform provider 0.62.0 only honours EXOSCALE_API_ENDPOINTfor half of its calls: the rest reach the real cloud and create billable resources.Upstream fixed that in v0.71.0; pin at least that version. feint refuses rather thanserve half an apply. See docs/limits.md."}So the refusal is about a version now, no longer about a client, and two
things went away with the old behaviour. feint up no longer stops at the
doorstep when a declaration asks for Terraform against Exoscale: there is no
veto left to hit. And the escape hatch FEINT_EXOSCALE_ALLOW_TERRAFORM no
longer exists, because it has nothing left to lift.
Key takeaways
- Scaleway is redirected with
api_url, Outscale with anapiblock whose path carries/api/v1. - The empty second plan is the real test: without it, the emulation may return resources differently from how it accepts them.
- Exoscale is wired up through
EXOSCALE_API_ENDPOINT,/v2segment included, and requires a provider 0.71.0 or newer: older ones are refused by user agent, because they split the apply between the emulator and a paying account. root_volumeacceptssbs_volume, which unblocks configurations that declare it.- A full cycle costs nothing: apply, empty plan, destroy, with no billed resource.