Skip to content
Français
Cloud medium

Test a Terraform configuration on Scaleway, Outscale and Exoscale

2 min de lecture

Read this page in French

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 api block, whose full path is a trap.
  • Wire up Exoscale through an environment variable, with a provider 0.71.0 or newer.
  • Write a root_volume the 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:

Fenêtre de terminal
terraform init
terraform apply -auto-approve
Apply 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.

Fenêtre de terminal
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_ENDPOINT
for 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 than
serve 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 an api block 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, /v2 segment 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_volume accepts sbs_volume, which unblocks configurations that declare it.
  • A full cycle costs nothing: apply, empty plan, destroy, with no billed resource.

Next steps

Is this site useful to you?

Fewer than 1% of readers support this site.

I maintain more than 700 free guides, with no ads and no tracking. Any support, even a symbolic one, helps cover hosting and keeps these resources free. Thank you for the help.

The form does not show? Open Ko-fi in a new tab.

Subscribe and follow my DevSecOps work on LinkedIn